使用ProcessBuilder执行Shell脚本后Java程序挂起问题求助
问题分析与解决方案
核心问题根源
- 主程序挂起:
process.waitFor()会持续等待子进程(Shell脚本)终止,但你的脚本启动了一个长期运行的Java服务(server.jar nogui),该服务不会自动退出,导致脚本本身也一直处于运行状态,主Java程序因此卡在waitFor()无法继续执行。 - 错误日志为空:你先调用
redirectError(new File(...))重定向错误流到文件,随后又调用redirectErrorStream(true)将错误流合并到标准输出,后者覆盖了前者的设置,导致error.txt始终无内容。
方案1:让Shell脚本启动后台进程,快速退出
修改Shell脚本,将Java服务放到后台运行,使脚本执行后立即终止,主程序就能正常获取退出状态并继续运行。
修改后的Shell脚本:
#!/bin/bash echo "Starting..." cd /path/to/ # 将Java进程后台运行,并重定向其输出到日志文件 java -Xms2G -Xmx2G -jar /path/to/server.jar nogui > server.log 2>&1 &
&:让Java进程在后台执行,脚本无需等待它结束,会直接退出。> server.log 2>&1:将Java进程的标准输出和错误输出统一写入server.log,避免干扰主程序,同时方便后续查看服务日志。
同步修正Java代码的错误流配置:
public static void main(String[] args) { System.out.println("Starting!"); File directory = new File("/path/to"); File startScript = new File(directory, "script.sh"); try { // 直接执行脚本,简化进程层级 ProcessBuilder processBuilder = new ProcessBuilder(startScript.getAbsolutePath()); processBuilder.directory(directory); // 单独将脚本的错误流写入文件,不合并到标准输出 processBuilder.redirectError(new File(directory, "error.txt")); Process process = processBuilder.start(); System.out.println("Process start code ran..."); int exitStatus = process.waitFor(); if (exitStatus == 0) { System.out.println("Test ran successfully!"); } else { System.out.println("Something went wrong. Exit status is " + exitStatus); } } catch (Exception exception) { exception.printStackTrace(); } }
- 移除
redirectErrorStream(true),确保错误流正确写入error.txt。 - 直接用脚本路径作为ProcessBuilder命令,减少不必要的进程嵌套。
方案2:主程序不等待子进程终止,同时处理输出
如果需要主程序继续运行,且不阻塞在waitFor(),可以通过线程处理子进程的输出流(避免缓冲区满导致子进程也挂起),主程序可继续执行其他逻辑。
示例Java代码:
public static void main(String[] args) { System.out.println("Starting!"); File directory = new File("/path/to"); File startScript = new File(directory, "script.sh"); try { ProcessBuilder processBuilder = new ProcessBuilder(startScript.getAbsolutePath()); processBuilder.directory(directory); // 合并错误流到标准输出,统一处理输出内容 processBuilder.redirectErrorStream(true); Process process = processBuilder.start(); System.out.println("Process start code ran..."); // 启动线程读取子进程输出,防止缓冲区溢出导致子进程阻塞 new Thread(() -> { try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) { String line; while ((line = reader.readLine()) != null) { System.out.println("Script output: " + line); } } catch (IOException e) { e.printStackTrace(); } }).start(); // 主程序继续执行后续逻辑 System.out.println("Main program continues to run..."); // 后续可通过process.isAlive()检查子进程状态 // if (process.isAlive()) { ... } } catch (Exception exception) { exception.printStackTrace(); } }
此方案无需修改Shell脚本,主程序不会挂起,同时能实时捕获脚本和Java服务的输出。
额外注意事项
- 确保Shell脚本有可执行权限:执行
chmod +x /path/to/script.sh,避免因权限问题导致执行失败。 - 若需要Java服务长期稳定运行,建议结合
nohup使用,避免终端关闭后进程被终止:nohup java -Xms2G -Xmx2G -jar /path/to/server.jar nogui > server.log 2>&1 &
内容的提问来源于stack exchange,提问作者Liam
相关产品推荐
相关产品推荐

