1. OSのプロセスとは何か、なぜJavaから起動するのか
プロセス: JVM から bash へ、そしてその逆
PC を起動してブラウザやメッセンジャー、ゲームを開くと、それぞれが別々の プロセス として動作します。オペレーティングシステムは各プログラムのために独自の「ミニ世界」を立ち上げ、メモリやCPU時間を割り当て、ファイルやネットワークへのアクセスを許可します。こうしてプログラム同士は並行して動きながら互いに干渉しません。
Java プログラムも例外ではありません。java MyApp を起動すると、システムは必要なリソースを備えた専用のプロセスを作成します。その中であなたのプログラムが動作し、計算したり描画したりファイルを読んだりと、必要な処理を行います。
しかし、Java だけでは足りないこともあります。たとえば、ファイルをまとめるためにアーカイバを起動したり、動画を処理するために ffmpeg を呼び出したり、PC にインストールされている Java のバージョンを確認したりしたい場合です。これが外部プロセスの起動です。Java がシステムに「このユーティリティを呼び出して」と指示し、その実行結果を受け取ります。
要するに、これはプログラムをより柔軟にする方法です。異なるツールを組み合わせたり、ルーチンを自動化したり、既存のシステムプロセスに自分のロジックを組み込んだりできます。ときには、コードで車輪の再発明をするよりも、外部コマンドに一部の処理を任せたほうが簡単です。
JVM と外部プロセスの違い
JVMプロセス — Java 仮想マシン上で実行されているあなたのプログラム。
外部プロセス — それ以外のあらゆるプログラム(電卓、Python スクリプト、コマンドライン、別の Java のインスタンスなど)。
2. ProcessBuilder クラス
昔の Java ではメソッド Runtime.getRuntime().exec() でプロセスを起動していました。しかしこれは便利でも安全でもなく、顕微鏡で釘を打つようなものでした。そこで Java 5 から ProcessBuilder クラスが登場し、外部プロセスをより柔軟かつ分かりやすく作成・設定・起動できるようになりました。
ProcessBuilder は、将来起動するプロセスのパラメータ(コマンド、引数、作業フォルダ、環境変数など)をあらかじめ指定できる「組み立て用」のオブジェクトです。
構文: プロセスの作成
ProcessBuilder pb = new ProcessBuilder("コマンド", "引数1", "引数2", ...);
- 最初の引数はコマンド名(たとえば "ls"、"dir"、"ping"、"java")。
- 残りはそのコマンドへのパラメータ。
例: ls(Linux/Mac) または dir(Windows)を実行
ProcessBuilder pb;
if (System.getProperty("os.name").toLowerCase().contains("win")) {
pb = new ProcessBuilder("cmd.exe", "/c", "dir");
} else {
pb = new ProcessBuilder("ls", "-l");
}
ちなみに Windows では dir、copy などは独立した実行ファイルではなく、コマンドプロンプト(cmd.exe)の組み込みコマンドです。そのため cmd.exe /c ... 経由で起動する必要があります。
例: シンプルなプロセスの起動
ProcessBuilder pb = new ProcessBuilder("echo", "Hello, Java!");
3. プロセス環境の設定
引数の渡し方。 コマンド引数は個別の文字列として渡します:
ProcessBuilder pb = new ProcessBuilder("ping", "google.com");
作業ディレクトリの指定。 デフォルトでは、プロセスはあなたのプログラムと同じフォルダで起動されます。別のディレクトリを明示的に指定することもできます:
pb.directory(new java.io.File("/tmp")); // Linux/Mac 用
pb.directory(new java.io.File("C:\\Temp")); // Windows 用
環境変数の変更。 各プロセスには独自の環境変数セット(environment variables)があります。追加・変更できます:
pb.environment().put("MY_VAR", "HelloFromJava");
外部プロセスが特定の環境変数を期待している場合などに有用です。
4. プロセスの起動
メソッド start()。設定が終わったら、プロセスを起動します:
Process process = pb.start();
メソッド start() は Process オブジェクトを返し、起動したプログラムを操作できます。出力を読んだり、入力に書き込んだり、終了させたりできます。
例外処理。 start() はコマンドが見つからない、権限がない、その他起動時のエラーが発生した場合に IOException をスローする可能性があります。
例:
try {
Process process = pb.start();
// プロセスを操作する...
} catch (IOException e) {
System.out.println("プロセスの起動エラー: " + e.getMessage());
}
5. 実践: シンプルなコマンドの起動
例1: フォルダ内のファイル一覧を表示する
import java.io.*;
public class ProcessDemo {
public static void main(String[] args) {
// OSに応じてコマンドを決定する
ProcessBuilder pb;
if (System.getProperty("os.name").toLowerCase().contains("win")) {
pb = new ProcessBuilder("cmd.exe", "/c", "dir");
} else {
pb = new ProcessBuilder("ls", "-l");
}
try {
Process process = pb.start();
// プロセスの出力 (stdout) を読む
BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream())
);
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
// プロセスの終了を待つ
int exitCode = process.waitFor();
System.out.println("プロセスは終了コードで終了しました: " + exitCode);
} catch (IOException | InterruptedException e) {
System.out.println("エラー: " + e.getMessage());
}
}
}
ここで行っていること
- OS を判別して適切なコマンドを選ぶ。
- ProcessBuilder をコマンド付きで作成する。
- start() でプロセスを起動する。
- プロセスの stdout から行を読み、画面に出力する。
- プロセスの終了を待つ(waitFor())。
- 戻りコードを出力する(0 は成功、それ以外はエラー)。
例2: java -version を実行する
ProcessBuilder pb = new ProcessBuilder("java", "-version");
try {
Process process = pb.start();
// java -version は stderr に出力するため getErrorStream() を読む
BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getErrorStream())
);
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
process.waitFor();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
重要な注意点: 一部のコマンド(たとえば java -version)は標準出力(stdout)ではなく標準エラー(stderr)に情報を出力します。そのため、process.getErrorStream() を読む必要がある場合があります。
6. クロスプラットフォーム性: Windows と Linux/Mac の違い
- コマンドやその引数が異なる場合がある。
- ファイルパスの書き方が異なる(C:\Temp と /tmp)。
- 一部のコマンド(ls、cat など)は Unix 系のみで、Windows では代替(dir、type)を使う。
- Windows では組み込みコマンドは cmd.exe /c コマンド 経由でしか起動できない。
OS 判別の例:
String os = System.getProperty("os.name").toLowerCase();
if (os.contains("win")) {
// Windows
} else if (os.contains("mac")) {
// macOS
} else if (os.contains("nix") || os.contains("nux")) {
// Linux
}
ヒント: クロスプラットフォーム対応を想定する場合は、ターゲットの OS で必ずテストしましょう。
7. 表: ProcessBuilder の主なメソッドと機能
| メソッド/フィールド | 用途 | 使用例 |
|---|---|---|
|
コマンドと引数を指定してプロセスを作成 | |
|
作業ディレクトリを指定 | |
|
環境変数の取得/変更 | |
|
プロセスを起動 | |
|
プロセスの stdout を取得 | |
|
プロセスの stderr を取得 | |
|
プロセスの stdin を取得 | |
|
プロセスの終了を待つ | |
|
プロセスの終了コードを取得 | |
8. 外部プロセスを起動する際のよくある誤り
エラー1: コマンドが見つからない。 コマンド名を誤ったりシステムに存在しない場合、IOException(Cannot run program ...)が発生します。例: Windows で ls を実行しようとする。
エラー2: 引数の渡し方が間違っている。 すべてのコマンドを1つの文字列にまとめないでください。正しい例: new ProcessBuilder("ping", "google.com")。誤った例: new ProcessBuilder("ping google.com")。
エラー3: OS の違いを考慮していない。 Linux で動くコマンドが Windows にはない場合や、その逆があります。常に OS を確認し、コマンドを調整してください。
エラー4: プロセスの出力を処理していない。 プロセスの出力を読まないと、バッファがあふれて「ハング」することがあります。出力を使う予定がなくても読み取り、必要なら破棄してください。
エラー5: ストリームを閉じていない。 使用後にプロセスのストリームを閉じないと、リソースリークの原因になります。
エラー6: 例外を処理していない。 外部プロセスの起動はリスクがあります。必ず try-catch を使い、エラーをユーザーに伝えてください。
GO TO FULL VERSION