Java 9 Process API Improvements – Java 9流程API的改进

最后修改: 2017年 3月 1日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

The process API in Java had been quite primitive prior to Java 5, the only way to spawn a new process was to use the Runtime.getRuntime().exec() API. Then in Java 5, ProcessBuilder API was introduced which supported a cleaner way of spawning new processes.

在Java 5之前,Java中的进程API是相当原始的,生成一个新进程的唯一方法是使用Runtime.getRuntime().exec() API。然后在Java 5中,ProcessBuilder API被引入,它支持以更简洁的方式生成新进程。

Java 9 is adding a new way of getting information about current and any spawned processes.

Java 9增加了一种新的方式来获取关于当前和任何产生的进程的信息。

In this article, we will look at both of these enhancements.

在这篇文章中,我们将探讨这两个方面的改进。

2. Current Java Process Information

2.当前的Java进程信息

We can now obtain a lot of information about the process via the API java.lang.ProcessHandle.Info API:

现在我们可以通过API java.lang.ProcessHandle.Info API获得很多关于进程的信息。

  • the command used to start the process
  • the arguments of the command
  • time instant when the process was started
  • total time spent by it and the user who created it

Here’s how we can do that:

下面是我们如何做到这一点。

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

It is important to note that java.lang.ProcessHandle.Info is a public interface defined within another interface java.lang.ProcessHandle. The JDK provider (Oracle JDK, Open JDK, Zulu, or others) should provide implementations to these interfaces in such a way that these implementations return the relevant information for the processes.

需要注意的是,java.lang.ProcessHandle.Info是定义在另一个接口java.lang.ProcessHandle中的一个公共接口。JDK 提供者(Oracle JDK、Open JDK、Zulu 或其他)应该为这些接口提供实现,使这些实现能够返回进程的相关信息。

The output depends on the operating system and Java version. Here’s an example of what the output can look like:

输出结果取决于操作系统和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. Spawned Process Information

3.生成的进程信息

It is also possible to get the process information of a newly spawned process. In this case, after we spawn the process and get an instance of the java.lang.Process, we invoke the toHandle() method on it to get an instance of java.lang.ProcessHandle.

也可以获得一个新产生的进程的进程信息。在这种情况下,在我们生成进程并获得java.lang.Process的实例后,我们对其调用toHandle()方法以获得java.lang.ProcessHandle的实例。

The rest of the details remain the same as in the section above:

其余的细节仍与上节相同。

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

4. Enumerating Live Processes in the System

4.列举系统中的活进程

We can list all the processes currently in the system, which are visible to the current process. The returned list is a snapshot at the time when the API was invoked, so it’s possible that some processes terminated after taking the snapshot or some new processes were added.

我们可以列出当前系统中所有的进程,这些进程对当前进程是可见的。返回的列表是调用API时的快照,所以有可能一些进程在拍摄快照后终止了,或者有一些新的进程被加入。

In order to do that, we can use the static method allProcesses() available in the java.lang.ProcessHandle interface which returns us a Stream of ProcessHandle:

为了做到这一点,我们可以使用java.lang.ProcessHandle接口中的静态方法allProcesses(),该方法为我们返回一个StreamProcessHandle:

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. Enumerating Child Processes

5.枚举子进程

There are two variants to do this:

有两种变体可以做到这一点。

  • get direct children of the current process
  • get all the descendants of the current process

The former is achieved by using the method children() and the latter is achieved by using the method descendants():

前者是通过使用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. Triggering Dependent Actions on Process Termination

6.进程终止时触发从属行动

We might want to run something on termination of the process. This can be achieved by using the onExit() method in the java.lang.ProcessHandle interface. The method returns us a CompletableFuture which provides the ability to trigger dependent operations when the CompletableFuture is completed.

我们可能想在进程终止时运行一些东西。这可以通过使用java.lang.ProcessHandle接口中的onExit()方法实现。该方法为我们返回一个CompletableFuture,它提供了在CompletableFuture完成时触发相关操作的能力。

Here, the CompletableFuture indicates the process has completed, but it doesn’t matter if the process has completed successfully or not. We invoke the get() method on the CompletableFuture, to wait for its completion:

在这里,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());
    });
}

The onExit() method is available in the java.lang.Process interface as well.

onExit()方法在java.lang.Process接口中也可用。

7. Conclusion

7.结论

In this tutorial, we covered interesting additions to the Process API in Java 9 that give us much more control over the running and spawned processes.

在本教程中,我们介绍了Java 9中对Process API的有趣补充,这些补充使我们对运行和生成的进程有了更多的控制。

The code used in this article can be found over on GitHub.

本文中使用的代码可以在GitHub上找到over