複数のスレッドを開始し、各exec()をdestroy()で実行すると、Javaプロセスの一部が破棄されず、プログラムの終了後も実行されます。問題を再現するコードを次に示します。 。開始するスレッドが多いほど、存続するプロセスが多くなり、destroy()の前のスリープが増えるほど、存続するプロセスが少なくなります(例として、InfiniteLoopを使用しました。実行中のプロセスならどれでもうまくいきます)。
編集:バグがOracleに報告され、回答を待っています。このテーマに関する知識/実験を自由に共有してください。
for(int i = 0; i < 100; i++)
{
new Thread(new Runnable()
{
public void run()
{
try
{
Process p = Runtime.getRuntime().exec(new String[]{"Java", "InfiniteLoop"});
Thread.sleep(1);
p.destroy();
}catch(IOException | InterruptedException e){e.printStackTrace();}
}
}).start();
}
p.waitFor();
の前にp.destroy();
を使用します。
これにより、前のプロセスを確実に完了できます。 p.destroyコマンドは、exec()
コマンドがアクションを実行するよりも早く呼び出されると思います。したがって、それは役に立たなくなります。
サブプロセスがstdoutまたはstderrに(意図的かどうかに関係なく)何かを書き込む場合、問題が発生する可能性があります。
"一部のネイティブプラットフォームでは、標準の入力ストリームと出力ストリームに限られたバッファサイズしか提供されないため、サブプロセスの入力ストリームの書き込みまたは出力ストリームの読み取りに失敗すると、サブプロセスがブロックされる可能性があります。デッドロック。 "
出典: http://www.javaworld.com/jw-12-2000/jw-1229-traps.html
Runtime.exec()を使用する必要がある場合は、記事全体を読む価値のあるIMOです。
これは、スレッドがdestroy呼び出しを実行する前に、メインプログラムが終了し、関連するすべてのスレッドが開始されたプロセスを実行したままにするためです。これを確認するには、破棄後にSystem.out呼び出しを追加するだけで、実行されないことがわかります。これを克服するには、メインメソッドの最後にThread.sleepを追加すると、孤立したプロセスがなくなります。以下は、プロセスを実行したままにしません。
public class ProcessTest {
public static final void main (String[] args) throws Exception {
for(int i = 0; i < 100; i++) {
new Thread(new Runnable()
{
public void run() {
try {
Process p = Runtime.getRuntime().exec(new String[]{"Java", "InfiniteLoop"});
Thread.sleep(1);
p.destroy();
System.out.println("Destroyed");
}catch(IOException e) {
System.err.println("exception: " + e.getMessage());
} catch(InterruptedException e){
System.err.println("exception: " + e.getMessage());
}
}
}).start();
}
Thread.sleep(1000);
}
}
プロセスへの入力/出力/エラーストリームを閉じる必要があります。過去に、これらのストリームが閉じられていないために(使用されていなくても)フォークされたプロセスが適切に完了しないという問題がいくつか見られました。
link によると、この呼び出しに応答して、オペレーティングシステムによって別個のプロセスが生成されると思います。このプロセスの有効期間は、Javaプログラムとその中のスレッドに依存しないため、プログラムが終了した後も実行を継続することが期待できます。自分のマシンで試してみたところ、次のように機能しているようです。予想:
import Java.io.*;
class Mp {
public static void main(String []args) {
for(int i = 0; i < 100; i++) {
new Thread(new Runnable() {
public void run() {
try {
System.out.println("1");
Process p = Runtime.getRuntime().exec
(new String[]{"notepad", ""});
System.out.println("2");
Thread.sleep(5);
System.out.println("3");
p.destroy();
System.out.println("4");
}
catch(IOException | InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
}
私は非常によく似た問題を抱えていて、destroy()
が機能しないという問題は単一のスレッドでも現れていました。
_Process process = processBuilder(ForeverRunningMain.class).start()
long endTime = System.currentTimeMillis() + TIMEOUT_MS;
while (System.currentTimeMillis() < endTime) {
sleep(50);
}
process.destroy();
_
_TIMEOUT_MS
_が低すぎる場合、プロセスは常に破棄されるとは限りませんでした。 sleep()
の前にdestroy()
を追加すると、修正されました(理由の説明はありませんが) :
_Thread.sleep(300);
process.destroy();
_
これは答えではありません。質問のコメントでの議論に従って、この問題を再現するための私自身の試みの完全なソースを投稿しています。
Ubuntu12.04ではこの問題を再現できません。 OpenJDK 6b_27(ただし、以下を参照)。
ProcessTest.Java:
import Java.io.*;
public class ProcessTest {
public static final void main (String[] args) throws Exception {
for(int i = 0; i < 100; i++) {
new Thread(new Runnable()
{
public void run() {
try {
Process p = Runtime.getRuntime().exec(new String[]{"Java", "InfiniteLoop"});
Thread.sleep(1);
p.destroy();
}catch(IOException e) {
System.err.println("exception: " + e.getMessage());
} catch(InterruptedException e){
System.err.println("exception: " + e.getMessage());
}
}
}).start();
}
}
}
InfiniteLoop.Java
public class InfiniteLoop {
public static final void main (String[] args) {
while (true) ;
}
}
JVMが終了した後もプロセスが実行されたままになるという問題を再現できません。ただし、スレッドを開始した後、メインから戻る前にメインスレッドに長い遅延を追加すると、約12の実行中のJavaプロセスが残ります(ただし、メインプログラムは終了します)。
更新:
終了後、約5つのプロセスを実行したままにしました。それは常に起こるわけではありません。奇妙な。これについてももっと知りたいです。プロセスの破壊が速すぎるか、ある種の競合状態に関係しているという予感があります。多分Java何かをフォークするか、destroy()があまりにも速く/間違った時間に呼び出された場合に処理しない新しいプロセスを作成するために何かをします。
プロセスがサブプロセスを生成した場合、destroy()によってそれらが強制終了されない可能性があることを示す古いバグを見つけました(ただし、マークは解決されていません)。 bugs.Sun.com/bugdatabase/view_bug.do?bug_id=4770092 使用しているJDKのバージョン。
同様の問題のように見えるものへの別の参照があります: 子プロセスを強制的に強制終了するJavaツール/メソッド そして私があなたの人生に混乱を加えただけなら謝りたいです、私は実際にはしませんプロセスをそれほど使用し、癖に精通していません。うまくいけば、他の誰かが決定的な答えで介入するでしょう。サブプロセスをうまく処理できないようで、Javaは何かをフォークしていると思います。それだけです。
Runtime.execがプロセスを開始するために新しいスレッドを開始するときと、そのプロセスにそれ自体を破棄するように指示するときとの間には、競合状態があります。
私はLinuxマシンを使用しているので、UNIXProcess.classファイルを使用して説明します。
Runtime.exec(...)
は新しいProcessBuilder
を作成し、それを起動します。これにより、UNIXマシン上で新しいUNIXProcess
インスタンスが作成されます。 UNIXProcess
のコンストラクターには、バックグラウンド(フォーク)スレッドでプロセスを実際に実行する次のコードブロックがあります。
_Java.security.AccessController.doPrivileged(
new Java.security.PrivilegedAction() {
public Object run() {
Thread t = new Thread("process reaper") {
public void run() {
try {
pid = forkAndExec(prog,
argBlock, argc,
envBlock, envc,
dir,
redirectErrorStream,
stdin_fd, stdout_fd, stderr_fd);
} catch (IOException e) {
gate.setException(e); /*remember to rethrow later*/
gate.exit();
return;
}
Java.security.AccessController.doPrivileged(
new Java.security.PrivilegedAction() {
public Object run() {
stdin_stream = new BufferedOutputStream(new
FileOutputStream(stdin_fd));
stdout_stream = new BufferedInputStream(new
FileInputStream(stdout_fd));
stderr_stream = new FileInputStream(stderr_fd);
return null;
}
});
gate.exit(); /* exit from constructor */
int res = waitForProcessExit(pid);
synchronized (UNIXProcess.this) {
hasExited = true;
exitcode = res;
UNIXProcess.this.notifyAll();
}
}
};
t.setDaemon(true);
t.start();
return null;
}
});
_
バックグラウンドスレッドが、UNIXプロセスIDであるフィールドpid
を設定していることに注意してください。これは、どのプロセスを強制終了するかをOSに指示するためにdestroy()
によって使用されます。
destroy()
が呼び出されたときにこのバックグラウンドスレッドが実行されたことを確認する方法がないため、実行される前にプロセスを強制終了しようとする可能性がありますOR pidフィールドが設定される前にプロセスを強制終了します。pidは初期化されていないため0です。したがって、destroyの呼び出しが早すぎると、_kill -9 0
_と同等になります。
UNIXProcess destroy()
には、これをほのめかすコメントもありますが、プロセスが開始する前ではなく、プロセスがすでに終了した後にのみ、destroyの呼び出しを検討します。
_// There is a risk that pid will be recycled, causing us to
// kill the wrong process! So we only terminate processes
// that appear to still be running. Even with this check,
// there is an unavoidable race condition here, but the window
// is very small, and OSes try hard to not recycle pids too
// soon, so this is quite safe.
_
Pidフィールドは揮発性としてマークされていないため、常に最新の値が表示されない場合があります。