Aprimoramentos da API de processo do Java 9
1. Visão geral
A API do processo em Java era bastante primitiva antes do Java 5, a única maneira de gerar um novo processo era usar a APIRuntime.getRuntime().exec(). Então, no Java 5, a APIProcessBuilder foi introduzida, oferecendo suporte a uma maneira mais limpa de gerar novos processos.
Java 9 está adicionando uma nova maneira de obter informações sobre os processos atuais e quaisquer processos gerados.
Neste artigo, veremos esses dois aprimoramentos.
2. Informações atuais do processo Java
Agora podemos obter muitas informações sobre o processo por meio da API APIjava.lang.ProcessHandle.Info:
-
o comando usado para iniciar o processo
-
os argumentos do comando
-
instante em que o processo foi iniciado
-
tempo total gasto por ele e pelo usuário que o criou
Veja como podemos fazer isso:
@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());
}
É importante observar quejava.lang.ProcessHandle.Info é uma interface pública definida em outra interfacejava.lang.ProcessHandle. O provedor JDK (Oracle JDK, Open JDK, Zulu ou outros) deve fornecer implementações para essas interfaces de forma que essas implementações retornem as informações relevantes para os processos.
3. Informações do processo gerado
Também é possível obter as informações do processo de um processo recém-gerado. Nesse caso, depois de gerar o processo e obter uma instância dejava.lang.Process, invocamos o métodotoHandle() nele para obter uma instância dejava.lang.ProcessHandle.
O restante dos detalhes permanece o mesmo da seção acima:
String javaCmd = ProcessUtils.getJavaCmd().getAbsolutePath();
ProcessBuilder processBuilder = new ProcessBuilder(javaCmd, "-version");
Process process = processBuilder.inheritIO().start();
ProcessHandle processHandle = process.toHandle();
4. Enumeração de processos ativos no sistema
Podemos listar todos os processos atualmente no sistema, que são visíveis para o processo atual. A lista retornada é um instantâneo no momento em que a API foi chamada, portanto, é possível que alguns processos tenham sido encerrados após a obtenção do instantâneo ou alguns novos processos tenham sido adicionados.
Para fazer isso, podemos usar o método estáticoallProcesses() disponível na interfacejava.lang.ProcessHandle que nos retorna umStream deProcessHandle:
@Test
public void givenLiveProcesses_whenInvokeGetInfo_thenSuccess() {
Stream liveProcesses = ProcessHandle.allProcesses();
liveProcesses.filter(ProcessHandle::isAlive)
.forEach(ph -> {
assertNotNull(ph.getPid());
assertEquals(true, ph.info()
.command()
.isPresent());
});
}
5. Enumerando Processos Filhos
Existem duas variantes para fazer isso:
-
obter filhos diretos do processo atual
-
obter todos os descendentes do processo atual
O primeiro é obtido usando o métodochildren() e o último é obtido usando o métododescendants():
@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 children
= ProcessHandle.current().children();
children.filter(ProcessHandle::isAlive)
.forEach(ph -> log.info("PID: {}, Cmd: {}",
ph.getPid(), ph.info().command()));
// and for descendants
Stream descendants
= ProcessHandle.current().descendants();
descendants.filter(ProcessHandle::isAlive)
.forEach(ph -> log.info("PID: {}, Cmd: {}",
ph.getPid(), ph.info().command()));
}
6. Disparando ações dependentes na rescisão do processo
Podemos querer executar algo no final do processo. Isso pode ser obtido usando o métodoonExit() na interfacejava.lang.ProcessHandle. O método nos retorna umCompletableFuture que fornece a capacidade de acionar operações dependentes quandoCompletableFuture é concluído.
Aqui, oCompletableFuture indica que o processo foi concluído, mas não importa se o processo foi concluído com sucesso ou não. Invocamos o métodoget() emCompletableFuture, para aguardar sua conclusão:
@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 onProcessExit
= processHandle.onExit();
onProcessExit.get();
assertEquals(false, processHandle.isAlive());
onProcessExit.thenAccept(ph -> {
log.info("PID: {} has stopped", ph.getPid());
});
}
O métodoonExit() também está disponível na interfacejava.lang.Process.
7. Conclusão
Neste tutorial, cobrimos adições interessantes à APIProcess em Java 9 que nos dá muito mais controle sobre os processos em execução e gerados.
O código usado neste artigo pode ser encontradoover on GitHub.