Java 9プロセスAPIの改良

1概要

Java 5の前のJavaのプロセスAPIは非常に原始的でした、新しいプロセスを生み出す唯一の方法は Runtime.getRuntime()。exec() APIを使うことでした。それからJava 5では、 ProcessBuilder APIが導入され、新しいプロセスを生み出すよりクリーンな方法をサポートしました。

Java 9は現在のプロセスおよび発生したプロセスに関する情報を取得する新しい方法を追加しています。

この記事では、これら両方の機能強化について説明します。

2現在のJavaプロセス情報

API java.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が呼び出された時点のスナップショットです。そのため、スナップショットの取得後に一部のプロセスが終了したか、またはいくつかの新しいプロセスが追加された可能性があります。

そのためには、 java.lang.ProcessHandle インターフェースで使用可能な静的メソッド allProcesses() を使用します。このメソッドは Stream of __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子プロセスの列挙

これを行うには2つの方法があります。

  • 現在のプロセスの直接の子を取得する

  • 現在のプロセスのすべての子孫を取得する

前者は 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() メソッドを使用することで実現できます。このメソッドはリンクを返します:/java-completablefuture[ CompletableFuture ]これは、 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());
    });
}

onExit() メソッドは java.lang.Process インターフェースでも使用できます。

7. 結論

このチュートリアルでは、Java 9の Process APIへの興味深い追加機能について説明しました。これにより、実行中のプロセスと生成されたプロセスをより細かく制御できます。

この記事で使用されているコードはhttps://github.com/eugenp/tutorials/tree/master/core-java-9[GitHubで利用可能]です。