Улучшения API в Java 9 Process

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

 

1. обзор

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

Java 9 добавляет новый способ получения информации о текущих и любых порожденных процессах.

В этой статье мы рассмотрим оба эти улучшения.

2. Текущая информация о процессе Java

Теперь мы можем получить много информации о процессе через APIjava.lang.ProcessHandle.Info API:

  • команда, используемая для запуска процесса

  • аргументы команды

  • момент времени, когда процесс был запущен

  • общее время, проведенное им и пользователем, который его создал

Вот как мы можем это сделать:

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

Для этого мы можем использовать статический методallProcesses(), доступный в интерфейсеjava.lang.ProcessHandle, который возвращает намStream изProcessHandle:

@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. Перечисление дочерних процессов

Для этого есть два варианта:

  • получить прямые дети текущего процесса

  • получить всех потомков текущего процесса

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

Возможно, мы захотим запустить что-нибудь по завершении процесса. Это может быть достигнуто с помощью методаonExit() в интерфейсеjava.lang.ProcessHandle. Метод возвращает намCompletableFuture, который дает возможность запускать зависимые операции, когдаCompletableFuture завершен.

ЗдесьCompletableFuture указывает, что процесс завершился, но не имеет значения, успешно ли он завершился или нет. Мы вызываем методget() дляCompletableFuture, чтобы дождаться его завершения:

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

МетодonExit() также доступен в интерфейсеjava.lang.Process.

7. Заключение

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

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