Runtime.exec()が実行しない場合

Java言語の一部として、java.langパッケージはすべてのJavaプログラムに暗黙的にインポートされます。このパッケージの落とし穴は頻繁に表面化し、ほとんどのプログラマーに影響を及ぼします。今月は、Runtime.exec()メソッドに潜む罠について説明します。

落とし穴4:Runtime.exec()が実行されない場合

このクラスjava.lang.RuntimegetRuntime()、現在のJavaランタイム環境を取得するという静的メソッドを備えています。これが、Runtimeオブジェクトへの参照を取得する唯一の方法です。その参照を使用して、Runtimeクラスのexec()メソッドを呼び出すことにより、外部プログラムを実行できます。開発者は、このメソッドを呼び出して、ヘルプページをHTMLで表示するためのブラウザを起動することがよくあります。

exec()コマンドには、次の4つのオーバーロードバージョンがあります。

  • public Process exec(String command);
  • public Process exec(String [] cmdArray);
  • public Process exec(String command, String [] envp);
  • public Process exec(String [] cmdArray, String [] envp);

これらのメソッドのそれぞれについて、コマンド(および場合によっては引数のセット)がオペレーティングシステム固有の関数呼び出しに渡されます。これによりProcess、Java VMに返されるクラスへの参照を使用して、オペレーティングシステム固有のプロセス(実行中のプログラム)が作成されます。オペレーティングシステムごとにProcess特定のサブクラスがProcess存在するため、このクラスは抽象クラスです。

これらのメソッドには、3つの可能な入力パラメーターを渡すことができます。

  1. 実行するプログラムとそのプログラムへの引数の両方を表す単一の文字列
  2. プログラムを引数から分離する文字列の配列
  3. 環境変数の配列

の形式で環境変数を渡しますname=valueexec()プログラムとその引数の両方に単一の文字列を含むバージョンを使用する場合、文字列はStringTokenizerクラスを介して区切り文字として空白を使用して解析されることに注意してください。

IllegalThreadStateExceptionに遭遇

に関連する最初の落とし穴Runtime.exec()IllegalThreadStateExceptionです。APIの一般的な最初のテストは、その最も明白なメソッドをコーディングすることです。たとえば、Java VMの外部にあるプロセスを実行するには、このexec()メソッドを使用します。外部プロセスが返す値を確認するexitValue()には、Processクラスのメソッドを使用します。最初の例では、Javaコンパイラ(javac.exe)の実行を試みます。

リスト4.1BadExecJavac.java

インポートjava.util。*; インポートjava.io. *; public class BadExecJavac {public static void main(String args []){try {Runtime rt = Runtime.getRuntime(); プロセスproc = rt.exec( "javac"); int exitVal = proc.exitValue(); System.out.println( "Process exitValue:" + exitVal); } catch(Throwable t){t.printStackTrace(); }}}

一連のBadExecJavacプロデュース:

E:\ classes \ com \ javaworld \ jpitfalls \ article2> java BadExecJavac java.lang.IllegalThreadStateException:プロセスはBadExecJavac.main(BadExecJavac.java:13)のjava.lang.Win32Process.exitValue(ネイティブメソッド)で終了していません 

外部プロセスがまだ完了していない場合、exitValue()メソッドはIllegalThreadStateException;をスローします。そのため、このプログラムは失敗しました。ドキュメントにはこの事実が記載されていますが、なぜこのメソッドは有効な答えが得られるまで待つことができないのですか?

Processクラスで使用可能なメソッドをさらに詳しく調べると、waitFor()それを正確に実行するメソッドが明らかになります。実際にwaitFor()は、exit値も返します。これは、相互に使用しexitValue()たりwaitFor()組み合わせたりするのではなく、どちらかを選択することを意味します。exitValue()代わりに使用できる唯一の時間はwaitFor()、プログラムが完了しない可能性のある外部プロセスの待機をブロックしたくない場合です。waitFor()メソッドを使用する代わりに、メソッドに呼び出さwaitForれたブールパラメーターを渡してexitValue()、現在のスレッドが待機するかどうかを決定することをお勧めします。ブール値はより有益ですexitValue()はこのメソッドのより適切な名前であり、2つのメソッドが異なる条件下で同じ機能を実行する必要はありません。このような単純な条件の識別は、入力パラメーターのドメインです。

したがって、このトラップを回避するには、をキャッチするIllegalThreadStateExceptionか、プロセスが完了するのを待ちます。

それでは、リスト4.1の問題を修正し、プロセスが完了するのを待ちましょう。リスト4.2では、プログラムは再度実行javac.exeを試み、外部プロセスが完了するのを待ちます。

リスト4.2BadExecJavac2.java

インポートjava.util。*; インポートjava.io. *; public class BadExecJavac2 {public static void main(String args []){try {Runtime rt = Runtime.getRuntime(); プロセスproc = rt.exec( "javac"); int exitVal = proc.waitFor(); System.out.println( "Process exitValue:" + exitVal); } catch(Throwable t){t.printStackTrace(); }}}

残念ながら、を実行しBadExecJavac2ても出力は生成されません。プログラムがハングし、完了しません。javacプロセスが完了しないのはなぜですか?

Runtime.exec()がハングする理由

JDKのJavadocドキュメントは、この質問に対する答えを提供します。

一部のネイティブプラットフォームは、標準の入力ストリームと出力ストリームに制限されたバッファサイズしか提供しないため、サブプロセスの入力ストリームの書き込みまたは出力ストリームの読み取りに失敗すると、サブプロセスがブロックされ、デッドロックが発生する可能性があります。

これは、よく引用されるアドバイスに示されているように、プログラマーがドキュメントを読んでいない場合にすぎません。ファインマニュアル(RTFM)を読んでください。答えは部分的にイエスです。この場合、Javadocを読むと、途中まで到達できます。外部プロセスへのストリームを処理する必要があることを説明していますが、その方法については説明していません。

ニュースグループでこのAPIに関するプログラマーの質問や誤解が多数あることから明らかなように、ここでは別の変数が働いています。ただしRuntime.exec()、Process APIは非常に単純に見えますが、APIの単純な、または明白な使用のために、その単純さはだまされています。エラーが発生しやすいです。ここでのAPI設計者向けの教訓は、単純な操作のために単純なAPIを予約することです。複雑になりがちな操作やプラットフォーム固有の依存関係は、ドメインを正確に反映する必要があります。抽象化が行き過ぎてしまう可能性があります。JConfigライブラリは、ハンドルファイルやプロセス操作へのより完全なAPIの例を提供します(詳細については、下記の参考文献を参照)。

それでは、JDKのドキュメントに従って、javacプロセスの出力を処理しましょう。あなたが実行すると、javac引数なしで、それはプログラムと利用可能なすべてのプログラムオプションの意味を実行する方法について説明し、使用文のセットを生成します。これがstderrストリームに送られることを知っているので、プロセスが終了するのを待つ前に、そのストリームを使い果たすプログラムを簡単に書くことができます。リスト4.3はそのタスクを完了します。このアプローチは機能しますが、一般的な解決策としては適切ではありません。したがって、リスト4.3のプログラムの名前はMediocreExecJavac;です。それは平凡な解決策しか提供しません。より良い解決策は、標準エラーストリームと標準出力ストリームの両方を空にすることです。そして、最善の解決策は、これらのストリームを同時に空にすることです(後で説明します)。

リスト4.3MediocreExecJavac.java

インポートjava.util。*; インポートjava.io. *; public class MediocreExecJavac {public static void main(String args []){try {Runtime rt = Runtime.getRuntime(); プロセスproc = rt.exec( "javac"); InputStream stderr = proc.getErrorStream(); InputStreamReader isr = new InputStreamReader(stderr); BufferedReader br = new BufferedReader(isr); 文字列行= 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(); }}}

MediocreExecJavac生成の実行:

E:\ classes \ com \ javaworld \ jpitfalls \ article2> java MediocreExecJavac使用法:javacここで、次のものが含まれます:-gすべてのデバッグ情報を生成します-g:noneデバッグ情報を生成しません-g:{lines、vars、source}一部のデバッグ情報のみを生成します-O最適化;クラスファイルのデバッグや拡大を妨げる可能性があります-nowarn警告を生成しません-verboseコンパイラの実行内容に関するメッセージを出力します-deprecation非推奨のAPIが使用されるソースの場所を出力します-classpathユーザークラスファイルの場所を指定します-sourcepath入力ソースファイルの場所を指定します-bootclasspathブートストラップクラスファイルの場所を上書きします-extdirsインストールされた拡張機能の場所を上書きします-d生成されたクラスファイルを配置する場所を指定します-encodingソースファイルで使用される文字エンコードを指定します-target特定のVMバージョンのクラスファイルを生成しますプロセスexitValue:2

したがって、MediocreExecJavac動作し、の終了値を生成します2。通常、の終了値0は成功を示します。ゼロ以外の値はエラーを示します。これらの出口値の意味は、特定のオペレーティングシステムによって異なります。の値を持つWin32エラー2は、「ファイルが見つかりません」エラーです。javacプログラムの後にソースコードファイルを付けてコンパイルすることを期待しているので、それは理にかなっています。

したがって、Runtime.exec()起動するプログラムが出力を生成するか、入力を期待する場合、2番目の落とし穴(永遠にぶら下がっている)を回避するには、入力ストリームと出力ストリームを処理するようにしてください。

コマンドが実行可能プログラムであると仮定する

Windowsオペレーティングシステムでは、やなどのRuntime.exec()実行不可能なコマンドに使用しようとすると、多くの新しいプログラマーが遭遇します。その後、彼らはの3番目の落とし穴に遭遇します。リスト4.4はまさにそれを示しています:dircopyRuntime.exec()

リスト4.4BadExecWinDir.java

インポートjava.util。*; インポートjava.io. *; public class BadExecWinDir {public static void main(String args []){try {Runtime rt = Runtime.getRuntime(); プロセスproc = rt.exec( "dir"); InputStream stdin = proc.getInputStream(); InputStreamReader isr = new InputStreamReader(stdin); BufferedReader br = new BufferedReader(isr); 文字列行= 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(); }}}

一連のBadExecWinDirプロデュース:

E:\classes\com\javaworld\jpitfalls\article2>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) 

前述のように、のエラー値2は「ファイルが見つかりません」を意味します。この場合、指定された実行可能ファイルdir.exeが見つかりませんでした。これは、ディレクトリコマンドがWindowsコマンドインタープリターの一部であり、個別の実行可能ファイルではないためです。Windowsコマンドインタープリターを実行するには、使用しているWindowsオペレーティングシステムに応じて、command.comまたはのいずれかを実行しますcmd.exe。リスト4.5は、Windowsコマンドインタープリターのコピーを実行してから、ユーザーが指定したコマンドを実行します(例:)dir

リスト4.5GoodWindowsExec.java