默認情況下 Java 程序運行在單一 JVM 進程中。但某些場景需要啟動獨立子進程:
| 特性 | Runtime.exec() | ProcessBuilder |
|---|---|---|
| API 風格 | 舊式、字符串命令 | 現代、列表參數、流式構建 |
| 參數處理 | 字符串解析易出錯 (空格、引號) | 列表每個元素一個參數,自動處理轉義 |
| 環境變量 | 繼承父進程,難以定製 | pb.environment() 可增刪改 |
| 工作目錄 | 無法指定 (系統默認) | pb.directory(File) 靈活指定 |
| 輸出流合併 | 手動處理 | pb.redirectErrorStream(true) 一鍵合併 stderr 到 stdout |
| 啟動方式 | exec() 直接返回 Process | start() 返回 Process |
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
public class ProcessDemo {
public static void main(String[] args) {
// 1. 構建命令 (列表形式,自動處理空格/特殊字符)
List cmd = Arrays.asList("java", "-version"); // 或 "ping", "www.google.com"
ProcessBuilder pb = new ProcessBuilder(cmd);
// 2. 設置工作目錄
pb.directory(new File("C:\\")); // Windows
// pb.directory(new File("/tmp")); // Linux
// 3. 定製環境變量
Map env = pb.environment();
env.put("MY_CUSTOM_VAR", "hello_process");
env.remove("UNWANTED_VAR");
// 4. 關鍵: 合併錯誤流到標準流,避免雙流讀取死鎖
pb.redirectErrorStream(true);
// 5. 可選: 重定向輸出到文件
// pb.redirectOutput(ProcessBuilder.Redirect.to(new File("output.log")));
// pb.redirectError(ProcessBuilder.Redirect.to(new File("error.log")));
try {
// 6. 啟動進程
Process process = pb.start();
// 7. 異步讀取輸出 (防止緩衝區滿導致子進程阻塞)
Thread outputReader = new Thread(() -> {
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println("[子進程輸出] " + line);
}
} catch (IOException e) {
e.printStackTrace();
}
});
outputReader.start();
// 8. 可選: 向子進程寫入輸入
// try (OutputStream stdin = process.getOutputStream()) {
// stdin.write("input data\n".getBytes());
// stdin.flush();
// }
// 9. 等待結束並獲取退出碼
int exitCode = process.waitFor();
outputReader.join(); // 確保輸出讀取完成
System.out.println("子進程退出碼: " + exitCode);
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
}
pb.redirectErrorStream(true) 合併 stderr 到 stdout,單流讀取。ProcessHandle + CompletableFuture 異步處理。
// 父進程寫入,子進程從 System.in 讀取
ProcessBuilder pb = new ProcessBuilder("python3", "script.py");
Process p = pb.start();
try (OutputStream stdin = p.getOutputStream();
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(stdin))) {
writer.write("line1\n");
writer.write("line2\n");
writer.flush(); // 必須 flush,否則數據停留在緩衝區
// writer.close() 會關閉子進程的 stdin (EOF),視業務決定是否關閉
} catch (IOException e) {
e.printStackTrace();
}
// 獲取當前進程信息
ProcessHandle self = ProcessHandle.current();
System.out.println("PID: " + self.pid());
System.out.println("命令: " + self.info().command().orElse(""));
System.out.println("參數: " + self.info().arguments().orElse(new String[0]));
System.out.println("啟動時間: " + self.info().startInstant().orElse(null));
System.out.println("用戶: " + self.info().user().orElse(""));
// 列出所有系統進程 (需權限)
ProcessHandle.allProcesses()
.filter(ph -> ph.info().command().orElse("").contains("java"))
.forEach(ph -> System.out.println("Java 進程 PID: " + ph.pid()));
// 銷毀進程樹 (包含子進程)
ProcessHandle.of(pid).ifPresent(ph -> ph.descendants().forEach(ProcessHandle::destroy));
Java 多進程編程核心是 ProcessBuilder (構建) + 多線程讀取流 (消費) + waitFor() (同步)。必須合併錯誤流或雙線程讀取 避免死鎖。JDK 9+ 引入 ProcessHandle 提供進程管理能力。複雜場景 (長期運行、交互式、大數據傳輸) 建議考慮 進程間通信 (IPC):Socket、命名管道、共享內存、gRPC、消息隊列,而非簡單的標準流重定向。