6 プロセスAPI

プロセスAPIを使用すると、ネイティブ・オペレーティング・システムのプロセスを起動し、これに関する情報を取得し、管理することができます。

このAPIを使用して、オペレーティング・システムのプロセスを操作できます。次に例を示します。

  • 任意のコマンドを実行します。

    • 実行中のプロセスをフィルタします

    • 出力をリダイレクトします

    • 1つのプロセスが終了したら別のプロセスが起動するようにスケジュールすることによって、種類が異なるコマンドやシェルを連結します

    • 残りのプロセスをクリーンアップします

  • コマンドの実行をテストします:

    • 一連のテストを実行します

    • ログを出力します

  • コマンドをモニターします。

    • 実行時間が長いプロセスをモニターし、終了した場合に再起動します

    • 使用状況の統計を収集します

プロセスAPIのクラスとインタフェース

プロセスAPIは、ProcessBuilderProcessProcessHandleProcessHandle.Infoのクラスおよびインタフェースで構成されます。

ProcessBuilderクラス

ProcessBuilderクラスを使用して、オペレーティング・システムのプロセスを作成および起動します。

プロセスの作成および起動方法の例は、「プロセスの作成」を参照してください。ProcessBuilderクラスでは様々なプロセス属性を管理します。次の表にサマリーを示します。

表6-1 ProcessBuilderクラスの属性と関連メソッド

プロセス属性 説明 関連メソッド
コマンド コールする外部プログラム・ファイルとその引数(ある場合)を指定する文字列。
環境 環境変数(およびその値)。これは、初期状態では現在のプロセスのシステム環境のコピーです(System.getEnv()メソッドに関する項を参照)。
作業ディレクトリ デフォルトでは、現在のプロセスの現在の作業ディレクトリです。
標準入力ソース デフォルトでは、プロセスは標準入力をパイプから読み取ります。Process.getOutputStreamメソッドによって返される出力ストリームを使用してこれにアクセスします。
標準出力と標準エラーの出力先 デフォルトでは、プロセスは標準出力と標準エラーをパイプに書き込みます。Process.getInputStreamメソッドとProcess.getErrorStreamメソッドによって返される入力ストリームを使用してこれらにアクセスします。「プロセスからの出力のリダイレクト」の例を参照してください。
redirectErrorStreamプロパティ 標準出力とエラー出力を2つの個別のストリームとして送信する(値をfalseにする)か、エラー出力を標準出力とマージする(値をtrueにする)かを指定します。

Processクラス

Processクラスのメソッドを使用して、ProcessBuilder.startメソッドとRuntime.execメソッドによって起動されたプロセスを制御します。次の表に、これらのメソッドのサマリーを示します。

次の表では、Processクラスのメソッドのサマリーを示します。

表6-2 Processクラスのメソッド

メソッド・タイプ 関連メソッド
プロセスが完了するまで待機します。
プロセスに関する情報を取得します。
入力、出力およびエラーの各ストリームを取得します。「プロセス終了時のonExitメソッドを使用したプロセスの処理」の例を参照してください。
直接子プロセスと間接子プロセスを取得します。
プロセスを破棄または終了します。
プロセスが終了するときに完了する予定のCompletableFutureインスタンスを返します。「プロセス終了時のonExitメソッドを使用したプロセスの処理」の例を参照してください。

ProcessHandleインタフェース

ProcessHandleインタフェースを使用してネイティブ・プロセスを識別および制御します。ProcessクラスはProcessHandleと異なり、ProcessBuilder.startメソッドとRuntime.execメソッドによって起動されたプロセスのみを制御します。ただし、Processクラスを使用してプロセス入力、出力およびエラー・ストリームにアクセスすることはできます。

「ストリームでのプロセスのフィルタリング」ProcessHandleインタフェースの例を参照してください。次の表では、このインタフェースのサマリーを示します。

表6-3 ProcessHandleインタフェースのメソッド

メソッド・タイプ 関連メソッド
すべてのオペレーティング・システム・プロセスを取得します。
プロセス・ハンドルを取得します。
プロセスに関する情報を取得します。
直接子プロセスと間接子プロセスのストリームを取得します。
プロセスを破棄します。
プロセスが終了するときに完了する予定のCompletableFutureインスタンスを返します。「プロセス終了時のonExitメソッドを使用したプロセスの処理」の例を参照してください。

ProcessHandle.Infoインタフェース

ProcessHandle.Infoインタフェースを使用して、プロセス(ProcessBuilder.startメソッドとネイティブ・プロセスによって作成されたプロセスを含む)に関する情報を取得します。

「プロセスに関する情報の取得」ProcessHandle.Infoインタフェースの例を参照してください。次の表では、このインタフェースのサマリーを示します。

表6-4 ProcessHandle.Infoインタフェースのメソッド

メソッド 説明
arguments() プロセスの引数をString配列として返します。
command() プロセスの実行可能ファイルのパス名を返します。
commandLine() プロセスのコマンドラインを返します。
startInstant() プロセスの開始時間を返します。
totalCpuDuration() プロセスの累積CPU時間の合計を返します。
user() プロセスのユーザーを返します。

プロセスの作成

プロセスを作成するには、まずProcessBuilderクラスにコマンド名とその引数などのプロセスの属性を指定します。次に、ProcessBuilder.startメソッドを使用してプロセスを起動します。これによってProcessインスタンスが返されます。

次の行によってプロセスが作成および起動されます。

  ProcessBuilder pb = new ProcessBuilder("echo", "Hello World!");
  Process p = pb.start();

次の引用では、setEnvTestメソッドによってhorsedocの2つの環境変数が設定され、次にechoコマンドによってこれらの環境変数(およびシステム環境変数HOME)の値が出力されます:

  public static void setEnvTest() throws IOException, InterruptedException {
    ProcessBuilder pb =
      new ProcessBuilder("/bin/sh", "-c", "echo $horse $dog $HOME").inheritIO();
    pb.environment().put("horse", "oats");
    pb.environment().put("dog", "treats");
    pb.start().waitFor();
  }

このメソッドの出力を次に示します(ホーム・ディレクトリは/home/adminであるとします)。

oats treats /home/admin

プロセスに関する情報の取得

Process.pidメソッドによってプロセスのネイティブ・プロセスIDが返されます。Process.infoメソッドによって、プロセスに関する追加の情報(実行可能ファイルのパス名、開始時間、ユーザーなど)が含まれるProcessHandle.Infoインスタンスが返されます。

次の引用では、getInfoTestメソッドによってプロセスが起動され、次にプロセスに関する情報が出力されます。

    public static void getInfoTest() throws IOException {
        ProcessBuilder pb = new ProcessBuilder("echo", "Hello World!");
        String na = "<not available>";
        Process p = pb.start();
        ProcessHandle.Info info = p.info();
        System.out.printf("Process ID: %s%n", p.pid());
        System.out.printf("Command name: %s%n", info.command().orElse(na));
        System.out.printf("Command line: %s%n", info.commandLine().orElse(na));

        System.out.printf("Start time: %s%n",
            info.startInstant().map((Instant i) -> i
                .atZone(ZoneId.systemDefault()).toLocalDateTime().toString())
                .orElse(na));

        System.out.printf("Arguments: %s%n",
            info.arguments().map(
                (String[] a) -> Stream.of(a).collect(Collectors.joining(" ")))
                .orElse(na));

        System.out.printf("User: %s%n", info.user().orElse(na));
    }

このメソッドによって、次のような出力が表示されます。

Process ID: 18761
Command name: /usr/bin/echo
Command line: echo Hello World!
Start time: 2017-05-30T18:52:15.577
Arguments: <not available>
User: administrator

ノート:

  • プロセスの属性はオペレーティング・システムによって異なり、すべての実装で使用できるわけではありません。また、プロセスに関する情報は、リクエストを作成したプロセスのオペレーティング・システム権限によって制限されます。

  • ProcessHandle.Infoインタフェースのすべてのメソッドでは、Optional<T>のインスタンスが返されます。戻り値が空かどうか常に確認してください。

プロセスからの出力のリダイレクト

デフォルトでは、プロセスは標準出力と標準エラーをパイプに書き込みます。アプリケーションでは、Process.getOutputStreamメソッドおよびProcess.getErrorStreamメソッドから返される入力ストリームを使用して、これらのパイプにアクセスできます。ただし、プロセスの起動前に、redirectOutputメソッドとredirectErrorメソッドを使用して、標準出力と標準エラーをファイルなどの他の出力先にリダイレクトすることができます。

次の引用では、redirectToFileTestメソッドによって標準入力がファイルout.tmpにリダイレクトされ、次にこのファイルが出力されます。

  public static void redirectToFileTest() throws IOException, InterruptedException {
    File outFile = new File("out.tmp");
    Process p = new ProcessBuilder("ls", "-la")
      .redirectOutput(outFile)
      .redirectError(Redirect.INHERIT)
      .start();
    int status = p.waitFor();
    if (status == 0) {
      p = new ProcessBuilder("cat" , outFile.toString())
        .inheritIO()
        .start();
      p.waitFor();
    }
  }

引用では、標準出力はファイルout.tmpにリダイレクトされます。標準エラーは起動したプロセスの標準エラーにリダイレクトされます。値Redirect.INHERITによって、サブプロセスI/Oのソースまたは出力先は現在のプロセスと同じに指定されます。inheritIO()メソッドへのコールはredirectInput(Redirect.INHERIT).redirectOuput(Redirect.INHERIT).redirectError(Redirect.INHERIT)と同等です。

ストリームでのプロセスのフィルタリング

ProcessHandle.allProcessesメソッドでは、現在のプロセスで参照可能なすべてのプロセスのストリームが返されます。コレクションから要素をフィルタするのと同じ方法を使用して、このストリームのProcessHandleインスタンスをフィルタできます。

次の引用では、filterProcessesTestメソッドによって、現在のユーザーが所有しているすべてのプロセスに関する情報が、親プロセスのプロセスIDでソートされて出力されます。

public class ProcessTest {

  // ...

  public static void main(String[] args) {
    ProcessTest.filterProcessesTest();
  }

  static void filterProcessesTest() {
    Optional<String> currUser = ProcessHandle.current().info().user();
    ProcessHandle.allProcesses()
        .filter(p1 -> p1.info().user().equals(currUser))
        .sorted(ProcessTest::parentComparator)
        .forEach(ProcessTest::showProcess);
  }

  static int parentComparator(ProcessHandle p1, ProcessHandle p2) {
    long pid1 = p1.parent().map(ph -> ph.pid()).orElse(-1L);
    long pid2 = p2.parent().map(ph -> ph.pid()).orElse(-1L);
    return Long.compare(pid1, pid2);
  }

  static void showProcess(ProcessHandle ph) {
    ProcessHandle.Info info = ph.info();
    System.out.printf("pid: %d, user: %s, cmd: %s%n",
      ph.pid(), info.user().orElse("none"), info.command().orElse("none"));
  } 

  // ...
}

allProcessesメソッドはネイティブ・オペレーティング・システムのアクセス制御によって制限されることに注意してください。また、すべてのプロセスは非同期に作成され終了するため、ストリーム内のプロセスがアライブであること、またはallProcessesメソッドをコールした後に他のプロセスが作成されていないことは保証されません。

プロセス終了時のonExitメソッドを使用したプロセスの処理

Process.onExitメソッドとProcessHandle.onExitメソッドによってCompletableFutureインスタンスが返されます。これを使用してプロセス終了時のタスクをスケジュールできます。または、アプリケーションでプロセスが終了するまで待機する場合は、onExit().get()をコールします。

次の引用では、startProcessesTestメソッドによって3つのプロセスが作成され、起動されます。その後、各プロセスでonExit().thenAccept(onExitMethod)がコールされます。onExitMethodによってプロセスID (PID)、終了ステータスおよびプロセスの出力が表示されます。

public class ProcessTest {

  // ...

  static public void startProcessesTest() throws IOException, InterruptedException {
    List<ProcessBuilder> greps = new ArrayList<>();
    greps.add(new ProcessBuilder("/bin/sh", "-c", "grep -c \"java\" *"));
    greps.add(new ProcessBuilder("/bin/sh", "-c", "grep -c \"Process\" *"));
    greps.add(new ProcessBuilder("/bin/sh", "-c", "grep -c \"onExit\" *"));
    ProcessTest.startSeveralProcesses (greps, ProcessTest::printGrepResults);      
    System.out.println("\nPress enter to continue ...\n");
    System.in.read();  
  }

  static void startSeveralProcesses (
    List<ProcessBuilder> pBList,
    Consumer<Process> onExitMethod)
    throws InterruptedException {
    System.out.println("Number of processes: " + pBList.size());
    pBList.stream().forEach(
      pb -> {
        try {
          Process p = pb.start();
          System.out.printf("Start %d, %s%n",
            p.pid(), p.info().commandLine().orElse("<na>"));
          p.onExit().thenAccept(onExitMethod);
        } catch (IOException e) {
          System.err.println("Exception caught");
          e.printStackTrace();
        }
      }
    );
  }
  
  static void printGrepResults(Process p) {
    System.out.printf("Exit %d, status %d%n%s%n%n",
      p.pid(), p.exitValue(), output(p.getInputStream()));
  }

  private static String output(InputStream inputStream) {
    String s = "";
    try (BufferedReader br = new BufferedReader(new InputStreamReader(inputStream))) {
      s = br.lines().collect(Collectors.joining(System.getProperty("line.separator")));
    } catch (IOException e) {
      System.err.println("Caught IOException");
      e.printStackTrace();
    }
    return s;
  }

  // ...
}

startProcessesTestメソッドによって次のように出力されます。プロセスの終了順序が開始順序と異なる場合があることに注意してください。

Number of processes: 3
Start 12401, /bin/sh -c grep -c "java" *
Start 12403, /bin/sh -c grep -c "Process" *
Start 12404, /bin/sh -c grep -c "onExit" *

Press enter to continue ...

Exit 12401, status 0
ProcessTest.class:0
ProcessTest.java:16

Exit 12404, status 0
ProcessTest.class:0
ProcessTest.java:8

Exit 12403, status 0
ProcessTest.class:0
ProcessTest.java:38

このメソッドはSystem.in.read()メソッドをコールして、すべてのプロセスが終了(およびthenAcceptメソッドで指定されたメソッドを実行)する前にプログラムが終了することを防ぎます。

残りのプログラム処理に進む前にプロセスが終了するまで待機する場合は、onExit().get()をコールします。

  static void startSeveralProcesses (
    List<ProcessBuilder> pBList, Consumer<Process> onExitMethod)
    throws InterruptedException {
    System.out.println("Number of processes: " + pBList.size());
    pBList.stream().forEach(
      pb -> {
        try {
          Process p = pb.start();
          System.out.printf("Start %d, %s%n",
            p.pid(), p.info().commandLine().orElse("<na>"));
          p.onExit().get();
          printGrepResults(p);          
        } catch (IOException|InterruptedException|ExecutionException e ) {
          System.err.println("Exception caught");
          e.printStackTrace();
        }
      }
    );
  }

次に示すように、ComputableFutureクラスには様々なメソッドが用意されており、これをコールしてプロセス終了時のタスクをスケジュールできます。

  • thenApply: 関数タイプのラムダ式(値を返すラムダ式)を使用する点以外はthenAcceptと同じです。

  • thenRun: 実行可能タイプの(仮パラメータまたは戻り値がない)ラムダ式を使用します。

  • thenApplyAsyc: ForkJoinPool.commonPool()からのスレッドで指定された関数を実行します。

ComputableFutureではFutureインタフェースが実装されるため、このクラスにも同期メソッドが含まれています。

  • get​(long timeout, TimeUnit unit): 必要に応じてプロセスが終了するまで(引数で指定された時間まで)待機します。

  • isDone: プロセスが完了している場合はtrueを返します。

機密プロセス情報へのアクセスの制御

プロセス情報には、ユーザーID、パス、コマンドの引数などの機密情報が含まれている場合があります。セキュリティ・マネージャを使用してプロセス情報へのアクセスを制御します。

標準アプリケーションとして実行している場合、ProcessHandleは他のプロセスに関する情報に対してネイティブ・アプリケーションと同じオペレーティング・システム権限を持っています。ただし、システム・プロセスに関する情報を使用することはできません。

アプリケーションでSecurityManagerクラスを使用してセキュリティ・ポリシーを実装している場合、それを有効化してプロセス情報にアクセスするには、セキュリティ・ポリシーでRuntimePermission("manageProcess")を付与する必要があります。この権限によって、ネイティブ・プロセスの終了とプロセスのProcessHandle情報へのアクセスが有効になります。この権限によって、独自に作成していないプロセスの識別と終了をコードで行えるようになることに注意してください。