近期在項目有一個需求,需要通過Java的Runtime.getRuntime().exec()執行外部的一個批處理腳本,發現在執行的時候,出現各種詭異的問題,腳本執行一半出現卡死。而腳本在終端上運行沒有任何問題。
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().getRuntime().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();
}
}
}
通過上述代碼可以看到,錯誤流被重定向到標準輸出流,那麼程序只需要處理標準輸出就可以了。