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. composer封装dd函数

    1.安装composer,本人用的phpstudy,打开php扩展php_openssl 2.切换到www目录下 cmd 命令 composer 第一个就是我们要找的 3.运行composer req ...

  2. 吴裕雄--天生自然HADOOP操作实验学习笔记:hive DDL

    实验目的 了解hive DDL的基本格式 了解hive和hdfs的关系 学习hive在hdfs中的保存方式 学习一些典型常用的hiveDDL 实验原理 有关hive的安装和原理我们已经了解,这次实验我 ...

  3. MY_0003:设置界面显示单位

    1,设置单位

  4. C++->10.3.6.设有两个按升序排列的二进制文件a和b,将他们合并成一个新的升序二进制数据文件file。

    #include<iostream.h> #include<stdlib.h> #include<string.h> #include<fstream.h&g ...

  5. ZooKeeper启动报错:My id 3 not in the peer list

    错误描述: 解决方法:查看zookeeper-3.4.2/conf目录下 编辑zoo.cfg文件 发现第三行有问题修改

  6. 简单记账本APP开发二

    今天主要是进行了适配器的编写,数据库的创建以及对完善了业务逻辑,简单的APP到此已经通过测试可以使用.

  7. 用cookie存值

    ////用Request获取到客户端Cookie 判断是否为空 //if (Request.Cookies["CheckTime"] == null) //{ // //创建Coo ...

  8. Typecho的卡哇伊小猫咪小插件(Live2D猫咪插件)

    之前看到一个博客,被它博客上的动态小猫咪给吸引了,这个纯粹就是一个在线撸猫的神器啊.但是在网上寻找一番,并没有找到合适的插件,或者说没有找到合适的模型,因此无奈之后,只能向该博主请教,在它写的博客上, ...

  9. ELK学习实验015:日志的自定义index配置

    前面使用json格式收集了nginx的日志,但是再index的显示是filebeat-*,现在使用自定义的index进行配置 但是再使用filebeat的7.4版本以后,有一个巨坑,就是按照网络的很多 ...

  10. Flutter简易顶部导航

    因为在AppBar的bottom参数中返回TabBar在平板模式的对齐模式是居中的且不可调整,所有将TabBar在title中返回 import 'package:flutter/material.d ...