Runtime.getRuntime.exec()执行linux脚本导致程序卡死问题
问题:

在Java程序中,通过Runtime.getRuntime().exec()执行一个Linux脚本导致程序被挂住,而在终端上直接执行这个脚本则没有任何问题。
原因: 
先来看Java代码:

public final static void process1(String[] cmdarray) {
        Process p = null;
        BufferedReader br = null;
        try {
            p = Runtime.getRuntime().exec(cmdarray);
            br = new BufferedReader(new InputStreamReader(p.getInputStream()));
            String line = null;
            while ((line = br.readLine()) != null) {
                System.out.println(line);
            }
            p.waitFor();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (p != null) {
                p.destroy();
            }
        }
    }

脚本内容很简单,主要内容是将一个指定的tar.gz文件解压到指定目录中。
程序被挂住后,查看进程列表,发现了几个可疑点:

neil@-bash:~/work/tgz$ps ux | grep dowjones
neil  2079   0.0  0.0  2435492    264 s001  R+   10:56上午   0:00.00 egrep dowjones
neil  2077   0.0  0.0  2435080    652   ??  S    10:56上午   0:00.24 tar xvf dowjones.tar.gz
neil  2073   0.0  0.0  2435488    792   ??  S    10:56上午   0:00.00 /bin/bash /Users/neil/bin/genova/genova_crm.sh /Users/neil/work/tgz/dowjones.tar.gz /Users/neil/work/dest/dowj

其中genova_crm.sh 就是要执行的脚本,tar xvf dowjones.tar.gz 就是执行解压的命令。
可以看到,程序卡在tar命令上,这个命令被挂住了,非常奇怪的事情。。。
再次查看JDK文档,发现Process的文档上说标准缓冲区大小有限,不正确操作输入输出流时可能导致程序挂住。
单独执行tar xvf dowjones.tar.gz命令时,发现有N多输出,而通过Java执行时,没有看到那些输出。
Java程序中只获取了标准输出流,没有获取错误输出流,那么有可能是错误输出缓冲区满而导致tar命令挂住。

解决方法: 
修改Java程序,标准输出流与错误输出流均要处理,保证输出缓冲区不会被堵住。具体作法是用一个异步线程读取标准输出,读完即扔,让主线程读取错误输出流:

public final static void process1(String[] cmdarray) {
        try {
            final Process p = Runtime.getRuntime().exec(cmdarray);
            new Thread(new Runnable() {

@Override
                publicvoid run() {
                    BufferedReader br = new BufferedReader(
                            new InputStreamReader(p.getInputStream()));
                    try {
                        while (br.readLine() != null)
                            ;
                        br.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
            BufferedReader br = null;
            br = new BufferedReader(new InputStreamReader(p.getErrorStream()));
            String line = null;
            while ((line = br.readLine()) != null) {
                System.out.println(line);
            }
            p.waitFor();
            br.close();
            p.destroy();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

重新执行,发现程序可以正常执行了,tar命令的回显被打印出来了。问题解决。
这可能跟特定的tar包有关,执行tar解压时,明显可以看到回显字符串中有些乱码,回显全部被输出到错误流了。

上述方法可以避免标准输出或错误输出缓冲区满从而挂住主程序的问题,但是需要同时处理两个流,有重复之嫌。如果能把标准输出和错误输出并为一个流,那只需要处理一个流即可。ProcessBuilder提供了这种能力。

创建Process有两种方式,一种就是上述的通Runtime.exec来得到,还有一种可以通ProcessBuilder.start()来产生一个Process实例。
ProcessBuilder可以先设置必要的参数数据,如命令、环境变量、工作目录、重定向错误流到标准输出,然后start()根据这些参数来生成一个Process实例,启动一个子进程来执行相应的命令。
代码如下:

public final static void process(String[] cmdarray) throws Throwable {
        ProcessBuilder pb = new ProcessBuilder(cmdarray);
        pb.redirectErrorStream(true);
        Process p = null;
        BufferedReader br = null;
        try {
            p = pb.start();
            br = new BufferedReader(new InputStreamReader(p.getInputStream()));
            String line = null;
            logger.info("Invoke shell: {}", StringUtils.join(cmdarray, " "));
            while ((line = br.readLine()) != null) {
                logger.info(line);
            }
            p.waitFor();
        } finally {
            if (br != null) {
                br.close();
            }
            if (p != null) {
                p.destroy();
            }
        }
    }

public final static void process(String[] cmdarray) throws Throwable {
        ProcessBuilder pb = new ProcessBuilder(cmdarray);
        pb.redirectErrorStream(true);
        Process p = null;
        BufferedReader br = null;
        try {
            p = pb.start();
            br = new BufferedReader(new InputStreamReader(p.getInputStream()));
            String line = null;
            logger.info("Invoke shell: {}", StringUtils.join(cmdarray, " "));
            while ((line = br.readLine()) != null) {
                logger.info(line);
            }
            p.waitFor();
        } finally {
            if (br != null) {
                br.close();
            }
            if (p != null) {
                p.destroy();
            }
        }
    }

通过上述代码可以看到,错误流被重定向到标准输出流,那么程序只需要处理标准输出就可以了。

而使用Runtime.getRuntime().exec() 调用外部程序, 获取"标准输出流", 老是阻塞. 在网上找了找, 觉得应该是"错误输出流"的问题. 故为"错误输出流"单开一个线程读取之, "标准输出流"就不再阻塞了。解决过程如下:

http://blog.csdn.net/andylao62/article/details/44564937

Runtime.getRuntime.exec()执行linux脚本导致程序卡死有关问题的更多相关文章

  1. Runtime.getRuntime.exec()执行linux脚本导致程序卡死问题

    rumtime程序执行中出现卡住,执行成果达不到预期的标准.查看输出流以及错误流程是否内存占满了.开两个线程来运行输出流程和错误流程. rumtime运行windows脚本执行是要添加执行环境 cmd ...

  2. Runtime.getRuntime().exec()实现Java调用python程序

    使用Runtime.getRuntime().exec()来实现Java调用python,调用代码如下所示: import java.io.BufferedReader; import java.io ...

  3. Java Runtime.getRuntime().exec() 执行带空格命令

    可执行文件路径如果包含空格,则在java中不能被获取到. 此时Debug一下,会发现 project=null. project.waitFor 的返回值为1.但是去源路径单击bat文件是可以正常运行 ...

  4. Android: 通过Runtime.getRuntime().exec调用底层Linux下的程序或脚本

    Android Runtime使得直接调用底层Linux下的可执行程序或脚本成为可能 比如Linux下写个测试工具,直接编译后apk中通过Runtime来调用 或者写个脚本,apk中直接调用,省去中间 ...

  5. 使用Runtime.getRuntime().exec()在java中调用python脚本

    举例有一个Python脚本叫test.py,现在想要在Java里调用这个脚本.假定这个test.py里面使用了拓展的包,使得pythoninterpreter之类内嵌的编译器无法使用,那么只能采用ja ...

  6. 使用Runtime.getRuntime().exec()方法的几个陷阱 (转)

    Process 子类的一个实例,该实例可用来控制进程并获得相关信息.Process 类提供了执行从进程输入.执行输出到进程.等待进程完成.检查进程的退出状态以及销毁(杀掉)进程的方法. 创建进程的方法 ...

  7. 关于Runtime.getRuntime().exec()产生阻塞的2个陷阱

    本文来自网易云社区 背景 相信做java服务端开发的童鞋,经常会遇到Java应用调用外部命令启动一些新进程来执行一些操作的场景,这时候就会使用到Runtime.getRuntime().exec(), ...

  8. [转]使用Runtime.getRuntime().exec()方法的几个陷阱

    Process 子类的一个实例,该实例可用来控制进程并获得相关信息.Process 类提供了执行从进程输入.执行输出到进程.等待进程完成.检查进程的退出状态以及销毁(杀掉)进程的方法. 创建进程的方法 ...

  9. 使用Runtime.getRuntime().exec()方法的几个陷阱

    Process 子类的一个实例,该实例可用来控制进程并获得相关信息.Process 类提供了执行从进程输入.执行输出到进程.等待进程完成.检查进程的退出状态以及销毁(杀掉)进程的方法. 创建进程的方法 ...

随机推荐

  1. 一些linux软件国内源

    1. ubuntu 版本号 Ubuntu 12.04 (LTS)代号为precise. Ubuntu 14.04 (LTS)代号为trusty. Ubuntu 15.04 代号为vivid. Ubun ...

  2. C语言 if

    C语言 if C语言支持最基本的三种程序运行结构:顺序结构.选择结构.循环结构. 顺序结构:程序按顺序执行,不发生跳转. 选择结构:依据是否满足条件,有选择的执行相应功能. 循环结构:依据条件是否满足 ...

  3. 从接口自动化测试框架设计到开发(二)操作json文件、重构json工具类

    用例模板里的请求数据多,看起来很乱,所以可以通过访问另外一个文件的方式获取请求数据 把请求数据都放在一个json文件中 取出login的内容: import json fp = open('G:/un ...

  4. 项目在UAP可以直接启动,但是在eclipse上面不能直接启动

    上面两个eclipse都是用友的开发人员给我的,左边是驻场开发给我的,但是没有教我怎么用和哪里好用,所以一直用UAP来做开发.右边的eclipse是提ISM问题,北京用友远程调试问题时给我的,而且终于 ...

  5. javaSE学习笔记(11)--- Map

    javaSE学习笔记(11)--- Map 1.Map集合 现实生活中,我们常会看到这样的一种集合:IP地址与主机名,身份证号与个人,系统用户名与系统用户对象等,这种一一对应的关系,就叫做映射.Jav ...

  6. vue自定义插件

    1.新建js文件 utils.js,自定义方法 let local = { say() { console.log('我是插件里面自定义的方法') } } export default { insta ...

  7. Cenos7下指定ftp用户限制在特定目录下(亲身实践)

    好了,废话不多说.上头下来个需求,让我给别人开个ftp账户,只能访问项目的目录,不能访问项目外的目录,就算cd切换目录也不行. 开始: 第一步;安装ftp,我用的是centos7,只需敲入命令 yum ...

  8. mysql数据库环境配置及部分问题

    亲身经历了MySQL初学者的痛苦,把主要问题和解决方法整理一下. 一.解压版环境配置 1.把压缩包解压到某盘符下. 解压后在类似“E:\mysql-5.7.22-winx64”这个文件夹中可以看到以上 ...

  9. urllib模块提供的urlretrieve()函数使用

    urllib模块提供的urlretrieve()函数,urlretrieve()方法直接将远程的数据下载到本地 注意:若是网站有反爬虫的话这个函数会返回 403 Forbidden 参数url:传入的 ...

  10. php函数的巧妙应用

    直接切入正题: 1.extract();函数从数组中把变量导入到当前的符号表中 对于数组中的每个元素,键名用于变量名,键值用于变量值. 第二个参数 type 用于指定当某个变量已经存在,而数组中又有同 ...