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上找到。