Java 需啟動外部進程(.exe、.sh、.bat)時,標準方案是 Runtime.exec() / ProcessBuilder;極少數需深度交互(控制台輸入輸出、共享內存)可考慮 JNI。本文對比兩種方案完整代碼與避坑指南。
public class ExternalLauncher {
public static void main(String[] args) throws IOException {
// 簡單啟動,無需交互
Process p = Runtime.getRuntime().exec("notepad.exe");
// 需捕獲輸出/錯誤流,防緩衝區滿導致卡死
ProcessBuilder pb = new ProcessBuilder("cmd.exe", "/c", "dir C:\\");
pb.redirectErrorStream(true); // 合併錯誤流到標準輸出
Process proc = pb.start();
try (BufferedReader br = new BufferedReader(
new InputStreamReader(proc.getInputStream(), "GBK"))) {
String line;
while ((line = br.readLine()) != null)
System.out.println(line);
}
int exitCode = proc.waitFor();
System.out.println("Exit code: " + exitCode);
}
}
ProcessBuilder 替代 Runtime.exec():支援目錄、環境變量、流重定向配置。new ProcessBuilder("\"C:\\Program Files\\app.exe\"", "arg1") 或列表形式自動處理引用。proc.waitFor(timeout, TimeUnit.SECONDS);舊版本需另起監控線程。流程:
native static void launch(String cmd);javac + javah 生成 .h 頭文件。JNIEXPORT void JNICALL Java_ClassName_launch(JNIEnv*, jclass, jstring):GetStringUTFChars → _spawnl/_execv/CreateProcess → ReleaseStringUTFChars.dll/.so),放入 java.library.path。System.loadLibrary("NativeLauncher") 載入。// C 代碼片段 (Windows)
JNIEXPORT void JNICALL Java_ExternalLauncher_launch(JNIEnv *env, jclass cls, jstring jcmd) {
const char *cmd = (*env)->GetStringUTFChars(env, jcmd, NULL);
_spawnl(_P_NOWAIT, cmd, cmd, NULL); // 非阻塞啟動
(*env)->ReleaseStringUTFChars(env, jcmd, cmd);
}
| 維度 | Runtime.exec/ProcessBuilder | JNI |
|---|---|---|
| 可移植性 | 跨平台(命令語法需適配) | 平台綁定,需各平台編譯庫 |
| 交互能力 | 標準流管道,無控制台 | 可附加控制台、共享內存、Hook API |
| 複雜度 | 低(純 Java) | 高(C/JNI/構建/部署/崩潰風險) |
| 安全性 | 受 SecurityManager 約束 | 繞過 JVM 安全機制,原生代碼崩潰導致 JVM 退出 |
| 適用場景 | 99% 外部程式啟動需求 | 極罕見:需攔截鍵盤輸入、注入 DLL、實時共享內存 |
首選 ProcessBuilder,功能完備、安全、可移植。JNI 僅作為最後手段,且必須嚴格隔離:原生代碼極簡、大量防禦性檢查、獨立測試、準備降級方案。正如文中所嘆:VB Shell() 一行搞定,Java 標準 API 雖繁瑣但守住了跨平台底線——這正是選擇 Java 的理由。