1. 概要

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

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

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

これを行うには2つのバリエーションがあります。

  • 現在のプロセスの直接の子を取得します
  • 現在のプロセスのすべての子孫を取得します

前者はメソッドchildren()を使用して実現され、後者はメソッド descendants()を使用して実現されます。

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. プロセス終了に対する依存アクションのトリガー

プロセスの終了時に何かを実行したい場合があります。 これは、 java.lang.ProcessHandleインターフェースのonExit()メソッドを使用して実現できます。 このメソッドはCompletableFutureを返します。これは、CompleteableFutureが完了したときに依存操作をトリガーする機能を提供します。

ここで、 CompleteableFuture はプロセスが完了したことを示しますが、プロセスが正常に完了したかどうかは関係ありません。 CompletableFutureget()メソッドを呼び出して、完了を待ちます。

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. 結論

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

この記事で使用されているコードは、GitHubにあります。