0 預備知識
1 不正確的調用exitValue
2不正確的調用waitFor
3 一種可接受的調用方式
4 調用認為是可執行程序的時候容易發生的錯誤
5 window執行的良好示例
6 不良好的重定向命令輸出
7 良好的重定向輸出示例
8 總結
9 問答
?
0?預備知識
Runtime類是一個與JVM運行時環境有關的Singleton類,有以下幾個值得注意的地方:
0.1 Runtime.getRuntime()可以取得當前JVM的運行時環境,這也是在Java中唯一得到運行時環境的方法。
0.2 Runtime上其他大部分的方法都是實例方法,也就是說每次進行運行時調用時都要用到getRuntime方法。
0.3 Runtime中的exit方法是退出當前JVM的方法,估計也是唯一的。System類中的exit實際上也是通過調用Runtime.exit()來退出JVM的。
Java對Runtime返回值的一般規則,0代表正常退出,非0代表異常中止,這只是Java的規則,在各個操作系統中總會發生一些小的混淆。
0.4 Runtime.addShutdownHook()方法可以注冊一個hook在JVM執行shutdown的過程中,方法的參數只要是一個初始化過但是沒有執行的Thread實例就可以。(注意,Java中的Thread都是執行過了就不值錢的哦)
0.5說到addShutdownHook這個方法就要說一下JVM運行環境是在什么情況下shutdown或者abort的。
Shutdown:當最后一個非精靈進程退出或者收到了一個用戶中斷信號、用戶登出、系統shutdown、Runtime的exit方法被調用時JVM會啟動shutdown的過程,在這個過程開始后,他會并行啟動所有登記的shutdown hook(注意是并行啟動,這就需要線程安全和防止死鎖)。當shutdown過程啟動后,只有通過調用halt方法才能中止shutdown的過程并退出JVM。
Abort:?abort退出時JVM就是停止運行但并不一定進行shutdown。這只有JVM在遇到SIGKILL信號或者windows中止進程的信號、本地方法發生類似于訪問非法地址一類的內部錯誤時會出現。這種情況下并不能保證shutdown hook是否被執行。
0.6 Runtime.exec()方法的所有重載。這里要注意的是:
public Process exec(String[] cmdarray, String[] envp, File dir);
這個方法中cmdArray是一個執行的命令和參數的字符串數組,數組的第一個元素是要執行的命令往后依次都是命令的參數,envp中是name=value形式的環境變量設置,如果子進程要繼承當前進程的環境時是null。
?
1?不正確的調用exitValue
Java代碼??
public?class?BadExecJavac?{??????public?static?void?main(String?args[])?{??????????try?{??????????????Runtime?rt?=?Runtime.getRuntime();??????????????Process?proc?=?rt.exec("java");??????????????int?exitVal?=?proc.exitValue();??????????????System.out.println("Process?exitValue:?"?+?exitVal);??????????}?catch?(Throwable?t)?{??????????????t.printStackTrace();??????????}??????}??}?? ?
輸出
Java代碼??
java.lang.IllegalThreadStateException:?process?has?not?exited??????at?java.lang.ProcessImpl.exitValue(Native?Method)??????at?BadExecJavac.main(BadExecJavac.java:26)?? ??
錯誤分析:
主要問題就是錯誤的調用了exitValue來取得外部命令的返回值。因為exitValue方法是非阻塞的,在調用這個方法時外部命令并沒有返回所以引起異常。阻塞形式的方法是waitFor,它會一直等待外部命令執行完畢,然后返回執行的結果。
當你在一個Process上調用waitFor方法時,當前線程是阻塞的,如果外部命令無法執行結束,那么你的線程就會一直阻塞下去,這種意外會影響我們程序的執行。所以在我們不能判斷外部命令什么時候執行完畢而我們的程序還需要繼續執行的情況下,我們就應該循環的使用exitValue來取得外部命令的返回狀態,并在外部命令返回時作出相應的處理。
?
2不正確的調用waitFor
?
Java代碼??
public?class?BadExecJavac2?{??????public?static?void?main(String?args[])?{??????????try?{??????????????Runtime?rt?=?Runtime.getRuntime();??????????????Process?proc?=?rt.exec("javac");??????????????int?exitVal?=?proc.waitFor();??????????????System.out.println("Process?exitValue:?"?+?exitVal);??????????}?catch?(Throwable?t)?{?????????????t.printStackTrace();??????????}??????}??}?? ? ? ? ?不幸的是,這個程序也無法執行完成,它沒有輸出但卻一直懸在那里!這是為什么那?
?
JDK文檔中的解釋:因為本地的系統對標準輸入和輸出所提供的緩沖池有效,所以錯誤的對標準輸出快速的寫入和從標準輸入快速的讀入都有可能造成子進程的鎖,甚至死鎖。
JDK僅僅說明為什么問題會發生,卻并沒有說明這個問題怎么解決。
解決方法就是:執行完外部命令后我們要控制好Process的所有輸入和輸出(視情況而定),//在這個例子里邊因為調用的是Javac,而他在沒有參數的情況下會將提示信息輸出到標準出錯,所以在下面的程序中我們要對此進行處理。
?
3?一種可接受的調用方式
Java代碼??
public?class?MediocreExecJavac?{??????public?static?void?main(String?args[])?{??????????try?{??????????????Runtime?rt?=?Runtime.getRuntime();??????????????Process?proc?=?rt.exec("javac");??????????????InputStream?stderr?=?proc.getErrorStream();??????????????InputStreamReader?isr?=?new?InputStreamReader(stderr);??????????????BufferedReader?br?=?new?BufferedReader(isr);??????????????String?line?=?null;??????????????System.out.println("<error></error>");??????????????while?((line?=?br.readLine())?!=?null)??????????????????System.out.println(line);??????????????System.out.println("");??????????????int?exitVal?=?proc.waitFor();??????????????System.out.println("Process?exitValue:?"?+?exitVal);??????????}?catch?(Throwable?t)?{??????????????t.printStackTrace();??????????}??????}??}?? 輸出:
Java代碼??
<error></error>??Usage:?javac?<options></options>?<source?files=""></source>????...????Process?exitValue:?2?? ??結果分析:
?
出來了結果。
為了處理好外部命令大量輸出的情況,你要確保你的程序處理好外部命令所需要的輸入或者輸出。
?
4?調用認為是可執行程序的時候容易發生的錯誤
Java代碼??
public?class?BadExecWinDir?{??????public?static?void?main(String?args[])?{??????????try?{??????????????Runtime?rt?=?Runtime.getRuntime();??????????????Process?proc?=?rt.exec("dir");??????????????InputStream?stdin?=?proc.getInputStream();??????????????InputStreamReader?isr?=?new?InputStreamReader(stdin);??????????????BufferedReader?br?=?new?BufferedReader(isr);??????????????String?line?=?null;??????????????System.out.println("<output></output>");??????????????while?((line?=?br.readLine())?!=?null)??????????????????System.out.println(line);??????????????System.out.println("");??????????????int?exitVal?=?proc.waitFor();??????????????System.out.println("Process?exitValue:?"?+?exitVal);??????????}?catch?(Throwable?t)?{??????????????t.printStackTrace();??????????}??????}??}?? ?
輸出:
Java代碼??
java.io.IOException:?Cannot?run?program?"dir":?CreateProcess?error=2,?...??????at?java.lang.ProcessBuilder.start(ProcessBuilder.java:460)??????at?java.lang.Runtime.exec(Runtime.java:593)??????at?java.lang.Runtime.exec(Runtime.java:431)??????at?java.lang.Runtime.exec(Runtime.java:328)??????at?BadExecWinDir.main(BadExecWinDir.java:29)?? ??原因分析:
?
因為dir命令是由windows中的解釋器解釋的,直接執行dir時無法找到dir.exe這個命令,所以會出現文件未找到這個2的錯誤。如果我們要執行這樣的命令,就要先根據操作系統的不同執行不同的解釋程序command.com?或者cmd.exe。
?
5 window執行的良好示例
Java代碼??
public?class?StreamGobbler?extends?Thread?{??????InputStream?is;??????String??????type;????????StreamGobbler(InputStream?is,?String?type)?{??????????this.is?=?is;??????????this.type?=?type;??????}????????public?void?run()?{??????????try?{??????????????InputStreamReader?isr?=?new?InputStreamReader(is);??????????????BufferedReader?br?=?new?BufferedReader(isr);??????????????String?line?=?null;??????????????while?((line?=?br.readLine())?!=?null)??????????????????System.out.println(type?+?">"?+?line);??????????}?catch?(IOException?ioe)?{??????????????ioe.printStackTrace();??????????}??????}??}?? Java代碼??
public?class?GoodWindowsExec?{??????public?static?void?main(String?args[])?{??????????if?(args.length?<?1)?{??????????????System.out.println("USAGE:?java?GoodWindowsExec?<cmd></cmd>");??????????????System.exit(1);??????????}??????????try?{??????????????String?osName?=?System.getProperty("os.name");??????????????String[]?cmd?=?new?String[3];??????????????if?(osName.equals("Windows?NT"))?{??????????????????cmd[0]?=?"cmd.exe";??????????????????cmd[1]?=?"/C";??????????????????cmd[2]?=?args[0];??????????????}?else?if?(osName.equals("Windows?95"))?{??????????????????cmd[0]?=?"command.com";??????????????????cmd[1]?=?"/C";??????????????????cmd[2]?=?args[0];??????????????}??????????????Runtime?rt?=?Runtime.getRuntime();??????????????System.out.println("Execing?"?+?cmd[0]?+?"?"?+?cmd[1]?+?"?"?+?cmd[2]);??????????????Process?proc?=?rt.exec(cmd);??????????????//?any?error?message???????????????StreamGobbler?errorGobbler?=?new?StreamGobbler(proc.getErrorStream(),?"ERROR");??????????????//?any?output???????????????StreamGobbler?outputGobbler?=?new?StreamGobbler(proc.getInputStream(),?"OUTPUT");??????????????//?kick?them?off??????????????errorGobbler.start();??????????????outputGobbler.start();??????????????//?any?error?????????????????int?exitVal?=?proc.waitFor();??????????????System.out.println("ExitValue:?"?+?exitVal);??????????}?catch?(Throwable?t)?{??????????????t.printStackTrace();??????????}??????}??}?? ?
輸出:
…
?
ExitValue: 0
?
原因分析:
就是cmd.exe /C +一個windows中注冊了后綴的文檔名,windows會自動地調用相關的程序來打開這個文檔。
不要假設你執行的程序是可執行的程序,要清楚自己的程序是單獨可執行的還是被解釋的,?這里還有一點,就是得到process的輸出的方式是getInputStream,這是因為我們要從Java?程序的角度來看,外部程序的輸出對于Java來說就是輸入,反之亦然。
?
?
6?不良好的重定向命令輸出
錯誤的認為exec方法會接受所有你在命令行或者Shell中輸入并接受的字符串。這些錯誤主要出現在命令作為參數的情況下,程序員錯誤的將所有命令行中可以輸入的參數命令加入到exec中。下面的例子中就是一個程序員想重定向一個命令的輸出。
Java代碼??
public?class?BadWinRedirect?{??????public?static?void?main(String?args[])?{??????????try?{??????????????Runtime?rt?=?Runtime.getRuntime();??????????????Process?proc?=?rt.exec("java?jecho?'Hello?World'?>?test.txt");??????????????//?any?error?message???????????????StreamGobbler?errorGobbler?=?new?StreamGobbler(proc.getErrorStream(),?"ERROR");??????????????//?any?output???????????????StreamGobbler?outputGobbler?=?new?StreamGobbler(proc.getInputStream(),?"OUTPUT");??????????????//?kick?them?off??????????????errorGobbler.start();??????????????outputGobbler.start();??????????????//?any?error?????????????????int?exitVal?=?proc.waitFor();??????????????System.out.println("ExitValue:?"?+?exitVal);??????????}?catch?(Throwable?t)?{??????????????t.printStackTrace();??????????}??????}??}?? ?
程序員的本意是將Hello World這個輸入重訂向到一個文本文件中,但是這個文件并沒有生成,jecho僅僅是將命令行中的參數輸出到標準輸出中,用戶覺得可以像dos中重定向一樣將輸出重定向到一個文件中,但這并不能實現,用戶錯誤的將exec認為是一個shell解釋器,但它并不是,如果你想將一個程序的輸出重定向到其他的程序中,你必須用程序來實現他。可用java.io中的包。
?
7?良好的重定向輸出示例
Java代碼??
public?class?StreamGobbler?extends?Thread?{??????InputStream?is;??????String??????type;??????OutputStream?os;??????StreamGobbler(InputStream?is,?String?type)?{??????????this(is,?type,?null);??????}??????StreamGobbler(InputStream?is,?String?type,?OutputStream?redirect)?{??????????this.is?=?is;??????????this.type?=?type;??????????this.os?=?redirect;??????}??????public?void?run()?{??????????try?{??????????????PrintWriter?pw?=?null;??????????????if?(os?!=?null)??????????????????pw?=?new?PrintWriter(os);??????????????InputStreamReader?isr?=?new?InputStreamReader(is);??????????????BufferedReader?br?=?new?BufferedReader(isr);??????????????String?line?=?null;??????????????while?((line?=?br.readLine())?!=?null)?{??????????????????if?(pw?!=?null)??????????????????????pw.println(line);??????????????????System.out.println(type?+?">"?+?line);??????????????}??????????????if?(pw?!=?null)??????????????????pw.flush();??????????}?catch?(IOException?ioe)?{??????????????ioe.printStackTrace();??????????}??????}??}?? Java代碼??
public?class?GoodWinRedirect?{??????public?static?void?main(String?args[])?{??????????args?=?new?String[1];??????????args[0]="g:\\out.txt";??????????if?(args.length?<?1)?{??????????????System.out.println("USAGE?java?GoodWinRedirect?<outputfile></outputfile>");??????????????System.exit(1);??????????}??????????try?{??????????????FileOutputStream?fos?=?new?FileOutputStream(args[0]);??????????????Runtime?rt?=?Runtime.getRuntime();??????????????Process?proc?=?rt.exec("java?jecho?'Hello?World'");??????????????//?any?error?message???????????????StreamGobbler?errorGobbler?=?new?StreamGobbler(proc.getErrorStream(),?"ERROR");??????????????//?any?output???????????????StreamGobbler?outputGobbler?=?new?StreamGobbler(proc.getInputStream(),?"OUTPUT",?fos);??????????????//?kick?them?off??????????????errorGobbler.start();??????????????outputGobbler.start();??????????????//?any?error?????????????????int?exitVal?=?proc.waitFor();??????????????System.out.println("ExitValue:?"?+?exitVal);??????????????fos.flush();??????????????fos.close();??????????}?catch?(Throwable?t)?{??????????????t.printStackTrace();??????????}??????}??}?? ?
?
8?總結
總結了幾條規則,防止我們在進行Runtime.exec()調用時出現錯誤。
?
- 在一個外部進程執行完之前你不能得到他的退出狀態
- 在你的外部程序開始執行的時候你必須馬上控制輸入、輸出、出錯這些流。
- 你必須用Runtime.exec()去執行程序
- 你不能象命令行一樣使用Runtime.exec()。
?
9?問答
問:為什么Runtime.exec("ls")沒有任何輸出??
答:調用Runtime.exec方法將產生一個本地的進程,并返回一個Process子類的實例,該實例可用于控制進程或取得進程的相關信息。
由于調用Runtime.exec方法所創建的子進程沒有自己的終端或控制臺,因此該子進程的標準IO(如stdin,stdou,stderr)都通過Process.getOutputStream(),Process.getInputStream(),Process.getErrorStream()方法重定向給它的父進程了。用戶需要用這些stream來向子進程輸入數據或獲取子進程的輸出。所以正確執行Runtime.exec("ls")的例程如下:?
Java代碼??
try?{??????Process?process?=?Runtime.getRuntime().exec(command);??????InputStreamReader?ir?=?new?InputStreamReader(process.getInputStream());??????LineNumberReader?input?=?new?LineNumberReader(ir);??????String?line;??????while?((line?=?input.readLine())?!=?null)??????????System.out.println(line);??}?catch?(java.io.IOException?e)?{??????System.err.println("IOException?"?+?e.getMessage());??}?(原文地址:http://jiangshuiy.iteye.com/blog/1674235)
轉載于:https://www.cnblogs.com/jianyungsun/p/7297381.html
總結
以上是生活随笔為你收集整理的[转]Java中Runtime.exec的一些事的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。