Перейти к основному содержимому

Усовершенствования API процесса Java 9

· 4 мин. чтения

1. Обзор

API процесса в Java был довольно примитивным до Java 5, единственным способом создать новый процесс было использование API Runtime.getRuntime().exec() . Затем в Java 5 был представлен API-интерфейс ProcessBuilder , который поддерживал более чистый способ создания новых процессов.

В 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, поэтому возможно, что некоторые процессы завершились после создания моментального снимка или были добавлены новые процессы.

Для этого мы можем использовать статический метод allProcesses() , доступный в интерфейсе java.lang.ProcessHandle , который возвращает нам поток 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() , а второе — с помощью метода потомки() :

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. Запуск зависимых действий при завершении процесса

Мы можем захотеть запустить что-то по завершении процесса. Этого можно добиться с помощью метода onExit() в интерфейсе java.lang.ProcessHandle . Метод возвращает нам 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. Заключение

В этом руководстве мы рассмотрели интересные дополнения к Process API в Java 9, которые дают нам гораздо больший контроль над запущенными и порожденными процессами.

Код, использованный в этой статье, можно найти на GitHub .