Runtime.exec导致虚拟机挂起问题分析

在Java虚拟机中调用外部执行程序时,通过Runtime.getRuntime()获得运行时环境,并通过exec方法执行外部命令。

exec()有四个重载:

public Process exec(String command);  
public Process exec(String [] cmdArray);  
public Process exec(String command, String [] envp);  
public Process exec(String [] cmdArray, String [] envp);

对于每个这样的方法,都会产生一个命令,并可带一组参数。随后创建一个特定操作系统的进程,返回的Process类将持有该程序返回Java虚拟机的引用。这个Procss类是一个抽象类,具体子类的实现依赖于不同的底层操作系统。

你可以通过三种可能的输入参数到这些方法:
1、一个字符串,表示程序执行和程序的任何参数。
2、一个字符串数组,通过参数来区分出程序的实现功能。
3、一个环境变量的数组。

建议采用命令数组和环境变量数组的exec

exec(String command…)的版本在内部实现上都通过StringTokenizer类解析成数组参数的版本。

注意:String[] envp数组中环境变量的格式为:名称=值。
建议:将全部shell环境带入到envp数组中。
下述命令用于获取全部环境变量。

#on windows
cmd /c set
#on unix
env
public static Properties getEnv() {
    Properties prop = new Properties();
    Process p = null;
    try {
        if (getSystemType() == SYSTEM_WINDOWS) {
            p = Runtime.getRuntime().exec("cmd /c set");
        } else {
            p = Runtime.getRuntime().exec("env");
        }
        BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));
        String line, key, value;
        while ((line = br.readLine()) != null) {
            int i = line.indexOf("=");
            if (i > -1) {
                key = line.substring(0, i);
                value = line.substring(i + 1);
                key = key.toUpperCase();
                value = value.trim();
                prop.setProperty(key, value);
            }
        }
    } catch (IOException e) {
    }
    return prop;
}

exec的返回值

Process的waitFor()方法,可准确地返回应用程序的返回值,返回值用于程序做进一步的判断处理。

为什么 Runtime.exec() 挂起

javadoc文档提供了这个问题的答案:

java.lang

Class Process


public abstract class Process extends Object

扫描二维码关注公众号,回复: 2738571 查看本文章

The ProcessBuilder.start() and Runtime.exec methods create a native process and return an instance of a subclass of Process that can be used to control the process and obtain information about it. The class Process provides methods for performing input from the process, performing output to the process, waiting for the process to complete, checking the exit status of the process, and destroying (killing) the process.

The methods that create processes may not work well for special processes on certain native platforms, such as native windowing processes, daemon processes, Win16/DOS processes on Microsoft Windows, or shell scripts. The created subprocess does not have its own terminal or console. All its standard io (i.e. stdin, stdout, stderr) operations will be redirected to the parent process through three streams (getOutputStream(), getInputStream(), getErrorStream()). The parent process uses these streams to feed input to and get output from the subprocess. Because some native platforms only provide limited buffer size for standard input and output streams, failure to promptly write the input stream or read the output stream of the subprocess may cause the subprocess to block, and even deadlock.

Since:
JDK1.0

因为不同操作系统只提供有限的缓冲区大小为标准输入和输出流,未能及时写输入流或读取输出流的子流程可能会导致子流程阻止,甚至死锁。

一句话,外部程序的输出流、错误流会输入到虚拟机进程中,而默认的缓冲区大小有限,当输出、错误打印量较大时,会阻塞进程的执行,虚拟机也可能会死锁。

Process类提供了获取外部进程输入、输出、错误流的方法。

getInputStream();  //对应执行程序的输出流,流入到虚拟机
getErrorStream();  //对应执行程序的错误流,流入到虚拟机
getOutputStream(); //对应执行程序的输入流,从虚拟机流出

 确保程序的输出和错误流被及时处理

为了避免外部程序大量输出阻塞虚拟机的缓冲区,导致挂起问题,需要及时处理外部程序的输出和错误流,处理外部程序的重定向流时需要采用线程方式,避免阻塞应用程序的执行。

/*
 *重定向流,用线程及时处理掉输入流
 */
public class StreamRedirector extends Thread{
    InputStream is;  
    boolean isOutput;  
    StreamRedirector(InputStream is,boolean isOutput)  
    {  
        this.is = is;
        this.isOutput = isOutput;
    }  
    public void run()  
    {  
        InputStreamReader isr = null;
        BufferedReader br = null;
        try {
            isr = new InputStreamReader(is);
            br = new BufferedReader(isr);
            String line = null;
            while ((line = br.readLine()) != null){
                if(isOutput){
                    System.out.println(line);
                }
            }
            br.close();
            isr.close();
        } catch (IOException ioe) {
            ioe.printStackTrace();
        } 
    }   
    }  
}
/*
 *执行外部命令,通过执行超时时间,保证该函数在规定时间内返回,超时的进程被强制终止,并返回-1
 */
public static int executeCommand(String command, long timeout) {
    try {
        Process process = null;
        String[] commands = new String[3];
        //采用操作系统shell执行命令,可以带入全部环境变量
        if(SysProperty.getSystemType() == SysProperty.SYSTEM_WINDOWS){  //判断操作系统类型
            commands[0] = new String("cmd");
            commands[1] = new String("/c");
        }else{
            commands[0] = new String("/bin/sh");
            commands[1] = new String("-c");
        }
        commands[2] = command;
        process = Runtime.getRuntime().exec(commands,envs);
        //错误流和输出流重定向
        StreamRedirector errors = new StreamRedirector(process.getErrorStream(), false);                
        StreamRedirector outputs = new StreamRedirector(process.getInputStream(), false);  
        errors.start();  
        outputs.start();  
        //超时处理
        Worker worker = new Worker(process);
        worker.start();
        try {
            if(timeout > 0){
                worker.join(timeout);
                if (worker.exit != null){
                    return worker.exit;
                }else{
                    return -1;
                }
            }else{
                while(worker.exit == null){
                    Thread.sleep(100);
                }
                return worker.exit;
            }
        } catch (InterruptedException ex) {
            worker.interrupt();
            Thread.currentThread().interrupt();
            return -1;
        } finally {
            process.destroy();
        }
    } catch (IOException e) {
        return -1;
    }
}
//执行线程
private static class Worker extends Thread {
    private Process process;
    private Integer exit = null;
    private Worker(Process process) {
        this.process = process;
    }
    public void run() {
        try {
            exit = process.waitFor();
        } catch (InterruptedException ignore) {
            return;
        }
    }
}

猜你喜欢

转载自blog.csdn.net/wienerxu/article/details/81607835