1. 概述
Java中的進程API在Java5之前還是非常原始的,開啓一個新進程唯一的方式是調用Runtime.getRuntime().exec()
,直到Java5發佈之後,一些更加簡明的開啓新進程方式被封裝到了ProcessBuilder
中。
而在Java9中,你也將能使用一種全新的方式來獲取當前系統所有正在運行的進程的相關信息。
話不多說,先睹爲快!
2. 當前Java進程的信息
我們通過調用java.lang.ProcessHandle.Info
可以獲取諸多進程的相關信息:
- 用於開啓此進程所使用的命令
- 命令中傳遞的參數
- 開啓時間
- 開啓者和總運行時間
下面是具體方式:
@Test
public void givenCurrentProcess_whenInvokeGetInfo_thenSuccess()
throws IOException {
ProcessHandle processHandle = ProcessHandle.current();
ProcessHandle.Info processInfo = processHandle.info();
assertNotNull(processHandle.getPid());
assertEquals(false, processInfo.arguments().isPresent());
assertEquals(true, processInfo.command().isPresent());
assertTrue(processInfo.command().get().contains("java"));
assertEquals(true, processInfo.startInstant().isPresent());
assertEquals(true,
processInfo.totalCpuDuration().isPresent());
assertEquals(true, processInfo.user().isPresent());
}
需要注意的是,java.lang.ProcessHandle.Info
是在另一個接口java.lang.ProcessHandle
中定義的公共接口。JDK 供應商(Oracle JDK, Open JDK, Zulu 或其他) 以上面這些方式來爲接口提供實現,以使它們能返回進程的相關信息。
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
流:
@Test
public void givenLiveProcesses_whenInvokeGetInfo_thenSuccess() {
Stream<ProcessHandle> liveProcesses = ProcessHandle.allProcesses();
liveProcesses.filter(ProcessHandle::isAlive)
.forEach(ph -> {
assertNotNull(ph.getPid());
assertEquals(true, ph.info()
.command()
.isPresent());
});
}
5. 枚舉子進程
這裏有兩種獲取方式:
- 獲取當前進程的直屬子級
- 獲取當前進程的所有子級
前者調用children()
,後者調用descendants()
:
@Test
public void givenProcess_whenGetChildProcess_thenSuccess()
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.getPid(), ph.info().command()));
// and for descendants
Stream<ProcessHandle> descendants
= ProcessHandle.current().descendants();
descendants.filter(ProcessHandle::isAlive)
.forEach(ph -> log.info("PID: {}, Cmd: {}",
ph.getPid(), ph.info().command()));
}
6. 進程終止時觸發相關操作
有時候有需求需要我們在某個進程終止時觸發某些操作,我們可以通過調用java.lang.ProcessHandle
接口中聲明的onExit()
方法來滿足。 該方法返回CompletableFuture
,可以在CompletableFuture
完成時觸發某些操作。
CompletableFuture
表示一個進程已經完成,它不關心進程以何種方式完成。通過顯式調用它的get()
方法來等待進程完成:
@Test
public void givenProcess_whenAddExitCallback_thenSuccess()
throws Exception {
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.getPid());
CompletableFuture<ProcessHandle> onProcessExit
= processHandle.onExit();
onProcessExit.get();
assertEquals(false, processHandle.isAlive());
onProcessExit.thenAccept(ph -> {
log.info("PID: {} has stopped", ph.getPid());
});
}
當然,也可以通過java.lang.Process
接口來調用onExit()
方法。