You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java调用Linux命令/脚本时定时任务每6小时执行异常排查

问题描述

Java调用Shell命令的代码

public String executeShellCommand(String linuxCommand) {

        String commandOutput;
        ProcessBuilder processBuilder;
        String [] command = {
                "/bin/bash",
                "-c",
                linuxCommand
        };

        try {
            log.debug("Making system call : [{}]",String.join(" ", command));
            // Execute the shell command
            processBuilder = new ProcessBuilder(command);
            Process p = processBuilder.start();
            BufferedReader br = new BufferedReader(
                    new InputStreamReader(p.getInputStream()));
            if ((commandOutput = br.readLine()) != null) {
                p.waitFor();
                log.trace("exit: " + p.exitValue());
                p.destroy();
                return commandOutput;
            }

        } catch (Exception e) {
            log.error("Exception in running shell command {}", linuxCommand, e);
        
            return null;
        }
       
        log.trace("There was no output for the given command {} that was executed", linuxCommand);
        return "";
    }

异常现象

  • JVM首次启动后执行该方法成功
  • 定时任务设为每6小时执行时,首次调度执行就失败,后续每次调度都失败
  • 定时任务设为每15或30分钟执行时,24小时内所有执行都正常成功

错误日志

java.io.IOException: Cannot run program "/bin/bash": error=0, Failed to exec spawn helper: pid: 2009, exit value: 127
        at java.base/java.lang.ProcessBuilder.start(ProcessBuilder.java:1143) ~[na:na]
        at java.base/java.lang.ProcessBuilder.start(ProcessBuilder.java:1073) ~[na:na]
        at com.oracle.gbucs.database.exporter.provisioning.service.ShellCommandService.executeShellCommand(ShellCommandService.java:41) ~[classes!/:2024.2.6]
        at com.oracle.gbucs.database.exporter.provisioning.service.ProvisioningService.runExporterProvisioningScript(ProvisioningService.java:395) ~[classes!/:2024.2.6]
        at com.oracle.gbucs.database.exporter.provisioning.service.ProvisioningService.provisionExporterForOneDatabase(ProvisioningService.java:167) ~[classes!/:2024.2.6]
        at com.oracle.gbucs.database.exporter.provisioning.controller.ProvisioningController.provisionExporters(ProvisioningController.java:106) ~[classes!/:2024.2.6]
        at com.oracle.gbucs.database.exporter.provisioning.controller.ProvisioningController.scheduled(ProvisioningController.java:142) ~[classes!/:2024.2.6]
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
        at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
        at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:84) ~[spring-context-5.3.21.jar!/:5.3.21]
        at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54) ~[spring-context-5.3.21.jar!/:5.3.21]
        at org.springframework.scheduling.concurrent.ReschedulingRunnable.run(ReschedulingRunnable.java:95) ~[spring-context-5.3.21.jar!/:5.3.21]
        at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:539) ~[na:na]
        at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264) ~[na:na]
        at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304) ~[na:na]
        at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136) ~[na:na]
        at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635) ~[na:na]
        at java.base/java.lang.Thread.run(Thread.java:833) ~[na:na]
Caused by: java.io.IOException: error=0, Failed to exec spawn helper: pid: 2009, exit value: 127
        at java.base/java.lang.ProcessImpl.forkAndExec(Native Method) ~[na:na]
        at java.base/java.lang.ProcessImpl.<init>(ProcessImpl.java:314) ~[na:na]
        at java.base/java.lang.ProcessImpl.start(ProcessImpl.java:244) ~[na:na]
        at java.base/java.lang.ProcessBuilder.start(ProcessBuilder.java:1110) ~[na:na]
        ... 19 common frames omitted

示例命令

统计服务器当前运行的oracledb_exporter脚本数量:

ps -ef | grep oracledb_exporter | grep -v grep | wc -l

该命令直接在命令行执行正常,但通过上述Java方法调用时失败。

环境信息

  • Java版本:17.0.5
  • 运行环境:远程服务器(SSH会话),已确认服务器存在/bin/bash

问题分析与解决方案

这个错误Failed to exec spawn helper是Java 17+版本的已知问题,结合调度间隔的差异,核心原因和解决方案如下:

核心原因

Java 9+引入了新的进程生成机制,当JVM长时间闲置(比如6小时未执行进程调用)后,系统可能清理临时文件,或者JVM内部的spawn helper相关资源被回收/失效,导致再次调用ProcessBuilder.start()时找不到spawn helper程序,抛出127错误(命令未找到)。而短间隔调度时,JVM频繁调用进程,相关资源不会被清理,因此能正常执行。

另外,SSH会话中运行的Java进程,可能因会话长时间闲置导致环境变量变化,进而让JVM无法定位spawn helper的路径。

解决方案

1. 添加JVM参数禁用新进程生成机制

启动Java进程时添加以下参数,强制使用旧的fork/exec机制,绕过spawn helper的问题:

-Djdk.lang.Process.launchMechanism=vfork

或:

-Djdk.lang.Process.launchMechanism=fork

2. 修复Java安装目录权限

Java 17的spawn helper程序通常位于$JAVA_HOME/lib/jspawnhelper(64位系统可能在$JAVA_HOME/lib/amd64/jspawnhelper),确保该文件有执行权限:

chmod +x $JAVA_HOME/lib/jspawnhelper

3. 优化Java调用Shell的代码

原代码存在输出读取不完整、未处理错误流的问题,可能加剧异常情况,修改后的代码示例:

public String executeShellCommand(String linuxCommand) {
    StringBuilder output = new StringBuilder();
    StringBuilder errorOutput = new StringBuilder();
    ProcessBuilder processBuilder = new ProcessBuilder("/bin/bash", "-c", linuxCommand);
    
    try {
        log.debug("Making system call : [{}]", String.join(" ", processBuilder.command()));
        Process p = processBuilder.start();
        
        // 读取标准输出
        try (BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()))) {
            String line;
            while ((line = br.readLine()) != null) {
                output.append(line).append("\n");
            }
        }
        
        // 读取错误输出,避免进程阻塞
        try (BufferedReader br = new BufferedReader(new InputStreamReader(p.getErrorStream()))) {
            String line;
            while ((line = br.readLine()) != null) {
                errorOutput.append(line).append("\n");
            }
        }
        
        int exitCode = p.waitFor();
        log.trace("exit: {}", exitCode);
        if (exitCode != 0) {
            log.error("Command failed with exit code {}: {}", exitCode, errorOutput);
            return null;
        }
        p.destroy();
        return output.length() > 0 ? output.toString().trim() : "";
        
    } catch (Exception e) {
        log.error("Exception in running shell command {}", linuxCommand, e);
        return null;
    }
}

4. 改用后台服务运行Java进程

不要在SSH会话中直接运行Java进程,改用systemd等后台服务启动,保证进程运行在稳定环境中,避免因SSH会话超时导致环境变量丢失或资源被回收。


内容的提问来源于stack exchange,提问作者helloworld

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 06:55:56