jinfeng_wang

          G-G-S,D-D-U!

          BlogJava 首頁 新隨筆 聯系 聚合 管理
            400 Posts :: 0 Stories :: 296 Comments :: 0 Trackbacks

          http://www.javaworld.com/javaworld/jw-12-2000/jw-1229-traps.html

          http://www.javaeye.com/post/283231



          那就首先說點Runtime類吧,他是一個與JVM運行時環境有關的類,這個類是Singleton的。我說幾個自己覺得重要的地方。

          1、Runtime.getRuntime()可以取得當前JVM的運行時環境,這也是在Java中唯一一個得到運行時環境的方法。

          2、Runtime上其他大部分的方法都是實例方法,也就是說每次進行運行時調用時都要用到getRuntime方法。

          3、Runtime中的exit方法是退出當前JVM的方法,估計也是唯一的一個吧,因為我看到System類中的exit實際上也是通過調用Runtime.exit()來退出JVM的,這里說明一下Java對Runtime返回值的一般規則(后邊也提到了),0代表正常退出,非0代表異常中止,這只是Java的規則,在各個操作系統中總會發生一些小的混淆。


          4、Runtime.addShutdownHook()方法可以注冊一個hook在JVM執行shutdown的過程中,方法的參數只要是一個初始化過但是沒有執行的Thread實例就可以。(注意,Java中的Thread都是執行過了就不值錢的哦)

          5、說到addShutdownHook這個方法就要說一下JVM運行環境是在什么情況下shutdown或者abort的。文檔上是這樣寫的,當最后一個非精靈進程退出或者收到了一個用戶中斷信號、用戶登出、系統shutdown、Runtime的exit方法被調用時JVM會啟動shutdown的過程,在這個過程開始后,他會并行啟動所有登記的shutdown hook(注意是并行啟動,這就需要線程安全和防止死鎖)。當shutdown過程啟動后,只有通過調用halt方法才能中止shutdown的過程并退出JVM。

          那什么時候JVM會abort退出那?首先說明一下,abort退出時JVM就是停止運行但并不一定進行shutdown。這只有JVM在遇到SIGKILL信號或者windows中止進程的信號、本地方法發生類似于訪問非法地址一類的內部錯誤時會出現。這種情況下并不能保證shutdown hook是否被執行。


          現在開始看這篇文章,呵呵。


          首先講的是Runtime.exec()方法的所有重載。這里要注意的有一點,就是public Process exec(String [] cmdArray, String [] envp);這個方法中cmdArray是一個執行的命令和參數的字符串數組,數組的第一個元素是要執行的命令往后依次都是命令的參數,envp我個人感覺應該和C中的execve中的環境變量是一樣的,envp中使用的是name=value的方式。


          <!--[if !supportLists]-->1、 <!--[endif]-->一個很糟糕的調用程序,代碼如下,這個程序用exec調用了一個外部命令之后馬上使用exitValue就對其返回值進行檢查,讓我們看看會出現什么問題。


          import java.util.*;
          import java.io.*;

          public class BadExecJavac
          {
          public static void main(String args[])
          {
          try
          {
          Runtime rt = Runtime.getRuntime();
          Process proc = rt.exec("javac");
          int exitVal = proc.exitValue();
          System.out.println("Process exitValue: " + exitVal);
          } catch (Throwable t)
          {
          t.printStackTrace();
          }
          }
          }

          A run of BadExecJavac produces:


          E:classescomjavaworldjpitfallsarticle2>java BadExecJavac
          java.lang.IllegalThreadStateException: process has not exited
          at java.lang.Win32Process.exitValue(Native Method)
          at BadExecJavac.main(BadExecJavac.java:13)


          這里看原文就可以了解,這里主要的問題就是錯誤的調用了exitValue來取得外部命令的返回值(呵呵,這個錯誤我也曾經犯過),因為exitValue這個方法是不阻塞的,程序在調用這個方法時外部命令并沒有返回所以造成了異常的出現,這里是由另外的方法來等待外部命令執行完畢的,就是waitFor方法,這個方法會一直阻塞直到外部命令執行結束,然后返回外部命令執行的結果,作者在這里一頓批評設計者的思路有問題,呵呵,反正我是無所謂阿,能用就可以拉。但是作者在這里有一個說明,就是exitValue也是有好多用途的。因為當你在一個Process上調用waitFor方法時,當前線程是阻塞的,如果外部命令無法執行結束,那么你的線程就會一直阻塞下去,這種意外會影響我們程序的執行。所以在我們不能判斷外部命令什么時候執行完畢而我們的程序還需要繼續執行的情況下,我們就應該循環的使用exitValue來取得外部命令的返回狀態,并在外部命令返回時作出相應的處理。


          2、對exitValue處改進了的程序

          import java.util.*;
          import java.io.*;

          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,而他在沒有參數的情況下會將提示信息輸出到標準出錯,所以在下面的程序中我們要對此進行處理。


          import java.util.*;
          import java.io.*;

          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("");
          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();
          }
          }
          }


          程序的運行結果為

          E:classescomjavaworldjpitfallsarticle2>java MediocreExecJavac

          Usage: javac

          where includes:
          -g Generate all debugging info
          -g:none Generate no debugging info
          -g:{lines,vars,source} Generate only some debugging info
          -O Optimize; may hinder debugging or enlarge class files
          -nowarn Generate no warnings
          -verbose Output messages about what the compiler is doing
          -deprecation Output source locations where deprecated APIs are used
          -classpath Specify where to find user class files
          -sourcepath Specify where to find input source files
          -bootclasspath Override location of bootstrap class files
          -extdirs Override location of installed extensions
          -d Specify where to place generated class files
          -encoding Specify character encoding used by source files
          -target Generate class files for specific VM version

          Process exitValue: 2


          哎,不管怎么說還是出來了結果,作者作了一下總結,就是說,為了處理好外部命令大量輸出的情況,你要確保你的程序處理好外部命令所需要的輸入或者輸出。


          下一個題目,當我們調用一個我們認為是可執行程序的時候容易發生的錯誤(今天晚上我剛剛犯這個錯誤,沒事做這個練習時候發生的)

          import java.util.*;
          import java.io.*;

          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("");
          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();
          }
          }
          }

          A run of BadExecWinDir produces:


          E:classescomjavaworldjpitfallsarticle2>java BadExecWinDir
          java.io.IOException: CreateProcess: dir error=2
          at java.lang.Win32Process.create(Native Method)
          at java.lang.Win32Process.(Unknown Source)
          at java.lang.Runtime.execInternal(Native Method)
          at java.lang.Runtime.exec(Unknown Source)
          at java.lang.Runtime.exec(Unknown Source)
          at java.lang.Runtime.exec(Unknown Source)
          at java.lang.Runtime.exec(Unknown Source)
          at BadExecWinDir.main(BadExecWinDir.java:12)


          說實在的,這個錯誤還真是讓我摸不著頭腦,我覺得在windows中返回2應該是沒有找到這個文件的緣故,可能windows 2000中只有cmd命令,dir命令不是當前環境變量能夠解釋的吧。我也不知道了,慢慢往下看吧。

          嘿,果然和作者想的一樣,就是因為dir命令是由windows中的解釋器解釋的,直接執行dir時無法找到dir.exe這個命令,所以會出現文件未找到這個2的錯誤。如果我們要執行這樣的命令,就要先根據操作系統的不同執行不同的解釋程序command.com 或者cmd.exe。

          作者對上邊的程序進行了修改

          import java.util.*;
          import java.io.*;

          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();
          }
          }
          }

          public class GoodWindowsExec
          {
          public static void main(String args[])
          {
          if (args.length < 1)
          {
          System.out.println("USAGE: java GoodWindowsExec ");
          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();
          }
          }
          }

          Running GoodWindowsExec with the dir command generates:


          E:classescomjavaworldjpitfallsarticle2>java GoodWindowsExec "dir *.java"
          Execing cmd.exe /C dir *.java
          OUTPUT> Volume in drive E has no label.
          OUTPUT> Volume Serial Number is 5C5F-0CC9
          OUTPUT>
          OUTPUT> Directory of E:classescomjavaworldjpitfallsarticle2
          OUTPUT>
          OUTPUT>10/23/00 09:01p 805 BadExecBrowser.java
          OUTPUT>10/22/00 09:35a 770 BadExecBrowser1.java
          OUTPUT>10/24/00 08:45p 488 BadExecJavac.java
          OUTPUT>10/24/00 08:46p 519 BadExecJavac2.java
          OUTPUT>10/24/00 09:13p 930 BadExecWinDir.java
          OUTPUT>10/22/00 09:21a 2,282 BadURLPost.java
          OUTPUT>10/22/00 09:20a 2,273 BadURLPost1.java
          ... (some output omitted for brevity)
          OUTPUT>10/12/00 09:29p 151 SuperFrame.java
          OUTPUT>10/24/00 09:23p 1,814 TestExec.java
          OUTPUT>10/09/00 05:47p 23,543 TestStringReplace.java
          OUTPUT>10/12/00 08:55p 228 TopLevel.java
          OUTPUT> 22 File(s) 46,661 bytes
          OUTPUT> 19,678,420,992 bytes free
          ExitValue: 0

          這里作者教了一個windows中很有用的方法,呵呵,至少我是不知道的,就是cmd.exe /C +一個windows中注冊了后綴的文檔名,windows會自動地調用相關的程序來打開這個文檔,我試了一下,的確很好用,但是好像文件路徑中有空格的話就有點問題,我加上引號也無法解決。

          這里作者強調了一下,不要假設你執行的程序是可執行的程序,要清楚自己的程序是單獨可執行的還是被解釋的,本章的結束作者會介紹一個命令行工具來幫助我們分析。

          這里還有一點,就是得到process的輸出的方式是getInputStream,這是因為我們要從Java 程序的角度來看,外部程序的輸出對于Java來說就是輸入,反之亦然。


          最后的一個漏洞的地方就是錯誤的認為exec方法會接受所有你在命令行或者Shell中輸入并接受的字符串。這些錯誤主要出現在命令作為參數的情況下,程序員錯誤的將所有命令行中可以輸入的參數命令加入到exec中(這段翻譯的不好,湊合看吧)。下面的例子中就是一個程序員想重定向一個命令的輸出。


          import java.util.*;
          import java.io.*;

          // StreamGobbler omitted for brevity

          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();
          }
          }
          }

          Running BadWinRedirect produces:


          E:classescomjavaworldjpitfallsarticle2>java BadWinRedirect
          OUTPUT>'Hello World' > test.txt
          ExitValue: 0

          程序員的本意是將Hello World這個輸入重訂向到一個文本文件中,但是這個文件并沒有生成,jecho僅僅是將命令行中的參數輸出到標準輸出中,用戶覺得可以像dos中重定向一樣將輸出重定向到一個文件中,但這并不能實現,用戶錯誤的將exec認為是一個shell解釋器,但它并不是,如果你想將一個程序的輸出重定向到其他的程序中,你必須用程序來實現他。可用java.io中的包。


          import java.util.*;
          import java.io.*;

          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();
          }
          }
          }

          public class GoodWinRedirect
          {
          public static void main(String args[])
          {
          if (args.length < 1)
          {
          System.out.println("USAGE java GoodWinRedirect ");
          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();
          }
          }
          }

          Running GoodWinRedirect produces:


          E:classescomjavaworldjpitfallsarticle2>java GoodWinRedirect test.txt
          OUTPUT>'Hello World'
          ExitValue: 0

          這里就不多說了,看看就明白,緊接著作者給出了一個監測命令的小程序

          import java.util.*;
          import java.io.*;

          // class StreamGobbler omitted for brevity

          public class TestExec
          {
          public static void main(String args[])
          {
          if (args.length < 1)
          {
          System.out.println("USAGE: java TestExec "cmd"");
          System.exit(1);
          }

          try
          {
          String cmd = args[0];
          Runtime rt = Runtime.getRuntime();
          Process proc = rt.exec(cmd);

          // any error message?
          StreamGobbler errorGobbler = new
          StreamGobbler(proc.getErrorStream(), "ERR");

          // any output?
          StreamGobbler outputGobbler = new
          StreamGobbler(proc.getInputStream(), "OUT");

          // kick them off
          errorGobbler.start();
          outputGobbler.start();

          // any error???
          int exitVal = proc.waitFor();
          System.out.println("ExitValue: " + exitVal);
          } catch (Throwable t)
          {
          t.printStackTrace();
          }
          }
          }

          對這個程序進行運行:
          E:classescomjavaworldjpitfallsarticle2>java TestExec "e:javadocsindex.html"
          java.io.IOException: CreateProcess: e:javadocsindex.html error=193
          at java.lang.Win32Process.create(Native Method)
          at java.lang.Win32Process.(Unknown Source)
          at java.lang.Runtime.execInternal(Native Method)
          at java.lang.Runtime.exec(Unknown Source)
          at java.lang.Runtime.exec(Unknown Source)
          at java.lang.Runtime.exec(Unknown Source)
          at java.lang.Runtime.exec(Unknown Source)
          at TestExec.main(TestExec.java:45)

          193在windows中是說這不是一個win32程序,這說明路徑中找不到這個網頁的關聯程序,下面作者決定用一個絕對路徑來試一下。

          E:classescomjavaworldjpitfallsarticle2>java TestExec
          "e:program filesnetscapeprogramnetscape.exe e:javadocsindex.html"
          ExitValue: 0


          好用了,這個我也試了一下,用的是IE。


          最后,作者總結了幾條規則,防止我們在進行Runtime.exec()調用時出現錯誤。


          <!--[if !supportLists]-->1、 <!--[endif]-->在一個外部進程執行完之前你不能得到他的退出狀態

          <!--[if !supportLists]-->2、 <!--[endif]-->在你的外部程序開始執行的時候你必須馬上控制輸入、輸出、出錯這些流。

          <!--[if !supportLists]-->3、 <!--[endif]-->你必須用Runtime.exec()去執行程序

          <!--[if !supportLists]-->4、 <!--[endif]-->你不能象命令行一樣使用Runtime.exec()。

          posted on 2007-05-18 11:28 jinfeng_wang 閱讀(1096) 評論(1)  編輯  收藏 所屬分類: javaZZ

          評論

          # re: Runtime.exec()的使用 (zz) 2008-03-15 09:51 GreatSnow Pai
          非常感謝你的指導!  回復  更多評論
            

          主站蜘蛛池模板: 大宁县| 嘉禾县| 涟源市| 安西县| 大英县| 中西区| 卢龙县| 鄂托克前旗| 河源市| 绥滨县| 宁城县| 嘉义市| 罗定市| 鄂托克前旗| 清河县| 区。| 承德县| 广安市| 通化市| 石柱| 固镇县| 临沧市| 开封县| 吉木乃县| 阜阳市| 榕江县| 南投市| 彰化市| 突泉县| 沁阳市| 剑阁县| 横山县| 华阴市| 尖扎县| 凤山市| 南丹县| 赤壁市| 松溪县| 延吉市| 文成县| 武安市|