1. 概述

在Java 5之前,Java的进程API相对原始,唯一的方法是使用Runtime.getRuntime().exec()。然后在Java 5中,引入了ProcessBuilder API,它提供了一种更整洁的方式来启动新进程。

Java 9新增了一种获取当前和子进程信息的方式。

在这篇文章中,我们将探讨这两种增强功能。

2. 当前Java进程信息

现在,我们可以通过java.lang.ProcessHandle.Info API获取大量关于进程的信息:

  • 启动进程时使用的命令
  • 命令的参数
  • 进程启动的时间点
  • 进程运行时间和创建者

以下是实现方法:

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());
}

需要注意的是,java.lang.ProcessHandle.Info是在java.lang.ProcessHandle接口内部定义的一个公共接口。JDK提供商(如Oracle JDK、OpenJDK、Zulu等)应提供这些接口的实现,以返回与进程相关的实际信息。

输出结果取决于操作系统和Java版本。以下是一个可能的输出示例:

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]

3. 子进程信息

我们也能够获取新启动进程的信息。在这种情况下,启动进程后,我们得到一个java.lang.Process实例,然后在其上调用toHandle()方法来获取java.lang.ProcessHandle实例。

其余细节与上述部分相同:

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

4. 列出系统中的活动进程

我们可以列出当前进程中可见的所有进程。返回的列表是API调用时的快照,因此在快照之后,一些进程可能已经终止,或者新进程可能已添加。

为此,我们可以使用java.lang.ProcessHandle接口中的静态方法allProcesses(),它返回一个ProcessHandle流:

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());
        });
}

5. 列出子进程

有两种方式来实现这个功能:

  • 获取当前进程的直接子进程
  • 获取当前进程的所有后代

前者通过children()方法实现,后者通过descendants()方法实现:

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()));
}

6. 在进程终止时触发依赖操作

我们可能希望在进程终止时执行某些操作。这可以通过java.lang.ProcessHandle接口的onExit()方法实现。该方法返回一个CompletableFuture,它提供了在CompletableFuture完成时触发依赖操作的能力。

在这里,CompletableFuture表示进程已完成,但不论进程是否成功完成,这都不重要。我们调用get()方法在CompletableFuture上等待其完成:

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()方法也存在于java.lang.Process接口中。

7. 总结

在这篇教程中,我们介绍了Java 9对Process API的一些有趣增强,使我们对运行和启动的进程有了更多控制。

本文中的代码可以在GitHub上找到。