2. 使用 Process 类编译和运行 Java 程序

我们先来看一个例子:通过 Process API 编译并运行另一个 Java 程序:

@Test
public void whenExecutedFromAnotherProgram_thenSourceProgramOutput3() throws IOException {
 
    Process process = Runtime.getRuntime()
      .exec("javac -cp src src\\main\\java\\com\\baeldung\\java9\\process\\OutputStreamExample.java");
    process = Runtime.getRuntime() 
      .exec("java -cp src/main/java com.baeldung.java9.process.OutputStreamExample");
    BufferedReader output = new BufferedReader(new InputStreamReader(process.getInputStream()));
    int value = Integer.parseInt(output.readLine());
 
    assertEquals(3, value);
}

这个功能在实际中用途广泛,比如自动化构建、代码执行引擎等场景。

3. 创建进程

Java 应用程序可以调用系统中的任意可执行程序(受操作系统权限限制)。

例如,使用 ProcessBuilder 来启动 Windows 的记事本:

ProcessBuilder builder = new ProcessBuilder("notepad.exe");
Process process = builder.start();

✅ 这种方式比直接调用 Runtime.getRuntime().exec() 更加灵活,支持设置环境变量、工作目录等。

4. 销毁进程

Process 类提供了销毁子进程的方法,但具体行为是平台相关的。

4.1. 通过引用销毁进程

ProcessBuilder builder = new ProcessBuilder("notepad.exe");
Process process = builder.start();
process.destroy(); // 尝试优雅关闭

4.2. 通过 PID 销毁进程

⚠️ 注意:这会终止不是由你创建的进程,请谨慎操作!

long pid = /* PID to kill */;
Optional<ProcessHandle> optionalProcessHandle = ProcessHandle.of(pid);
optionalProcessHandle.ifPresent(processHandle -> processHandle.destroy());

4.3. 强制销毁进程

destroy() 不生效时,可以使用 destroyForcibly()

ProcessBuilder builder = new ProcessBuilder("notepad.exe");
Process process = builder.start();
process.destroy();
if (process.isAlive()) {
    process.destroyForcibly();
}

📌 建议顺序:先 destroy(),再检查是否还活着,最后才考虑强制终止。

5. 等待进程完成

有两个重载方法用于等待进程结束。

5.1. waitFor()

阻塞当前线程直到子进程退出:

ProcessBuilder builder = new ProcessBuilder("notepad.exe");
Process process = builder.start();
assertThat(process.waitFor() >= 0);

5.2. waitFor(long timeout, TimeUnit unit)

带超时机制的版本:

ProcessBuilder builder = new ProcessBuilder("notepad.exe");
Process process = builder.start();
assertFalse(process.waitFor(1, TimeUnit.SECONDS));

返回值说明:

  • true: 子进程已退出
  • false: 超时退出

6. 获取进程退出码:exitValue()

该方法不会阻塞,如果进程未结束会抛出 IllegalThreadStateException。否则返回进程的退出码(通常为非负整数):

@Test
public void 
  givenSubProcess_whenCurrentThreadWillNotWaitIndefinitelyforSubProcessToEnd_thenProcessExitValueReturnsGrt0() 
  throws IOException {
    ProcessBuilder builder = new ProcessBuilder("notepad.exe");
    Process process = builder.start();
    assertThat(process.exitValue() >= 0);
}

❌ 注意:不要在进程未结束前调用,否则会抛异常。

7. 判断进程是否存活:isAlive()

快速判断进程是否仍在运行:

ProcessBuilder builder = new ProcessBuilder("notepad.exe");
Process process = builder.start();
Thread.sleep(10000);
process.destroy();
assertTrue(process.isAlive());

8. 处理进程流

默认情况下,子进程没有自己的控制台,其标准输入/输出/错误会被重定向到父进程。

8.1. getErrorStream()

获取错误输出流:

@Test
public void givenSubProcess_whenEncounterError_thenErrorStreamNotNull() throws IOException {
    Process process = Runtime.getRuntime().exec(
      "javac -cp src src\\main\\java\\com\\baeldung\\java9\\process\\ProcessCompilationError.java");
    BufferedReader error = new BufferedReader(new InputStreamReader(process.getErrorStream()));
    String errorString = error.readLine();
    assertNotNull(errorString);
}

8.2. getInputStream()

获取标准输出流:

@Test
public void givenSourceProgram_whenReadingInputStream_thenFirstLineEquals3() throws IOException {
    Process process = Runtime.getRuntime().exec(
      "javac -cp src src\\main\\java\\com\\baeldung\\java9\\process\\OutputStreamExample.java");
    process = Runtime.getRuntime()
      .exec("java -cp  src/main/java com.baeldung.java9.process.OutputStreamExample");
    BufferedReader output = new BufferedReader(new InputStreamReader(process.getInputStream()));
    int value = Integer.parseInt(output.readLine());
 
    assertEquals(3, value);
}

8.3. getOutputStream()

向子进程发送输入数据:

Writer w = new OutputStreamWriter(process.getOutputStream(), "UTF-8");
w.write("send to child\n");

8.4. 过滤进程流

可以根据条件筛选运行中的进程:

@Test
public void givenRunningProcesses_whenFilterOnProcessIdRange_thenGetSelectedProcessPid() {
    assertThat(((int) ProcessHandle.allProcesses()
      .filter(ph -> (ph.pid() > 10000 && ph.pid() < 50000))
      .count()) > 0);
}

9. Java 9 对 Process API 的增强

Java 9 引入了更丰富的 API 来管理和监控进程。

9.1. 获取当前进程信息

private static void infoOfCurrentProcess() {
    ProcessHandle processHandle = ProcessHandle.current();
    ProcessHandle.Info processInfo = processHandle.info();

    log.info("PID: " + processHandle.pid());
    log.info("Arguments: " + processInfo.arguments());
    log.info("Command: " + processInfo.command());
    log.info("Instant: " + processInfo.startInstant());
    log.info("Total CPU duration: " + processInfo.totalCpuDuration());
    log.info("User: " + processInfo.user());
}

示例输出:

16:31:24.784 [main] INFO  c.b.j.process.ProcessAPIEnhancements - PID: 22640
16:31:24.790 [main] INFO  c.b.j.process.ProcessAPIEnhancements - Arguments: Optional[[Ljava.lang.String;@2a17b7b6]
16:31:24.791 [main] INFO  c.b.j.process.ProcessAPIEnhancements - Command: Optional[/Library/Java/JavaVirtualMachines/jdk-13.0.1.jdk/Contents/Home/bin/java]
16:31:24.795 [main] INFO  c.b.j.process.ProcessAPIEnhancements - Instant: Optional[2021-08-31T14:31:23.870Z]
16:31:24.795 [main] INFO  c.b.j.process.ProcessAPIEnhancements - Total CPU duration: Optional[PT0.818115S]
16:31:24.796 [main] INFO  c.b.j.process.ProcessAPIEnhancements - User: Optional[username]

9.2. 获取新创建进程的信息

String javaCmd = ProcessUtils.getJavaCmd().getAbsolutePath();
ProcessBuilder processBuilder = new ProcessBuilder(javaCmd, "-version");
Process process = processBuilder.inheritIO().start();
ProcessHandle processHandle = process.toHandle();

9.3. 枚举系统中所有可见进程

private static void infoOfLiveProcesses() {
    Stream<ProcessHandle> liveProcesses = ProcessHandle.allProcesses();
    liveProcesses.filter(ProcessHandle::isAlive)
        .forEach(ph -> {
            log.info("PID: " + ph.pid());
            log.info("Instance: " + ph.info().startInstant());
            log.info("User: " + ph.info().user());
        });
}

9.4. 枚举子进程或后代进程

private static void infoOfChildProcess() throws IOException {
    int childProcessCount = 5;
    for (int i = 0; i < childProcessCount; i++) {
        String javaCmd = ProcessUtils.getJavaCmd()
          .getAbsolutePath();
        ProcessBuilder processBuilder
          = new ProcessBuilder(javaCmd, "-version");
        processBuilder.inheritIO().start();
    }

    Stream<ProcessHandle> children = ProcessHandle.current()
      .children();
    children.filter(ProcessHandle::isAlive)
      .forEach(ph -> log.info("PID: {}, Cmd: {}", ph.pid(), ph.info()
        .command()));
    Stream<ProcessHandle> descendants = ProcessHandle.current()
      .descendants();
    descendants.filter(ProcessHandle::isAlive)
      .forEach(ph -> log.info("PID: {}, Cmd: {}", ph.pid(), ph.info()
        .command()));
}

9.5. 在进程终止时触发回调

private static void infoOfExitCallback() throws IOException, InterruptedException, ExecutionException {
    String javaCmd = ProcessUtils.getJavaCmd()
      .getAbsolutePath();
    ProcessBuilder processBuilder
      = new ProcessBuilder(javaCmd, "-version");
    Process process = processBuilder.inheritIO()
      .start();
    ProcessHandle processHandle = process.toHandle();

    log.info("PID: {} has started", processHandle.pid());
    CompletableFuture onProcessExit = processHandle.onExit();
    onProcessExit.get();
    log.info("Alive: " + processHandle.isAlive());
    onProcessExit.thenAccept(ph -> {
        log.info("PID: {} has stopped", ph.pid());
    });
}

📌 onExit() 返回的是 CompletableFuture<ProcessHandle>,可以链式处理后续逻辑。

10. 总结

本文详细介绍了 Java 中的 Process API,包括基本操作如创建、销毁、等待进程结束,以及对进程流的处理。同时讲解了 Java 9 中新增的强大功能,如获取进程信息、枚举系统进程、监听进程退出等。

这些 API 在自动化部署、容器化管理、脚本执行等高级场景中有广泛应用。

源码见 GitHub


原始标题:Guide to java.lang.Process API | Baeldung