Contents

Java 9 Process API改进

1. 概述

Java 中的进程 API 在 Java 5 之前非常原始,产生新进程的唯一方法是使用Runtime.getRuntime().exec() API。然后在 Java 5 中,引入了ProcessBuilder API,它支持一种更简洁的方式来生成新进程。 Java 9 添加了一种获取有关当前进程和任何衍生进程的信息的新方法。 在本文中,我们将介绍这两种增强功能。

2. 当前Java进程信息

我们现在可以通过 API 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、Open JDK、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表示流程已经完成,但流程是否成功完成并不重要。我们调用CompletableFuture上的*get()*方法,等待其完成:

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接口中也可用。