线程启动的运行进程不会破坏(Java)

启动多个线程并使每个exec()然后destroy()一个正在运行的java进程导致某些进程没有被销毁并且在程序退出后仍然运行。 这是一些重现问题的代码。 我注意到你开始的线程越多,进程就越活跃。 而且,在destroy()之前睡眠越多,进程就越少。 (我使用InfiniteLoop作为示例。任何正在运行的进程都可以解决问题。)

编辑:已经向Oracle报告了Bug,等待答案。 随意分享有关该主题的任何知识/实验。

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

如果子进程向stdout或stderr写入任何内容(有意或无意),则可能会导致麻烦:

由于某些本机平台仅为标准输入和输出流提供有限的缓冲区大小,因此无法及时写入输入流或读取子进程的输出流可能导致子进程阻塞,甚至死锁。

资料来源: http : //www.javaworld.com/jw-12-2000/jw-1229-traps.html

如果您需要使用Runtime.exec(),那么整篇文章都值得一读。

使用p.waitFor();p.destroy();之前p.destroy();

这将确保完成前一个过程。 我认为p.destroy命令比exec()命令执行操作更早被调用。 因此它变得无用。

这只是因为在线程执行destroy调用之前,主程序终止并且所有关联的线程都会使启动的进程运行。 要validation这一点,只需在destroy之后添加一个System.out调用,你就会发现它没有被执行。 为了克服这个问题,在main方法的末尾添加一个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); } 

}

您应该关闭进程的输入/输出/错误流。 我们在过去看到了一些问题,分叉过程没有正确完成,因为这些流没有被关闭(即使它们没有被使用)。

我相信根据链接 ,操作系统会响应此调用产生一个独特的进程。 此进程的生命周期与您的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(); } } } 

这不是答案; 根据问题评论中的讨论,我发布了我自己尝试重新创建此问题的完整源代码。

我不能在Ubuntu 12.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终止后进程仍在运行的问题。 但是,如果我在启动线程之后但在从main返回之前在主线程中添加了一个很长的延迟,我确实看到大约十几个正在运行的java进程(尽管它们在主程序终止时终止)。

更新:

我刚刚让它在终止后运行了大约5个进程。 它并不总是发生。 奇怪的。 我也想了解更多相关信息。 我有一种预感,它与过快地破坏过程或某种竞争条件有关; 也许java forks off off或做某事来创建一个新的进程,如果在错误的时间调用太快/ destroy,则destroy()不会处理。

我发现了一个旧的bug(但它没有得到标记解决),声明如果一个进程产生子进程,它们可能不会被destroy()杀死。 bugs.sun.com/bugdatabase/view_bug.do?bug_id=4770092您使用的是哪个版本的JDK。

这是对类似问题的另一个参考: 强制杀死子进程的Java工具/方法如果我只为你的生活添加了混乱,我想道歉,我实际上并没有使用Process那么多而且不是熟悉怪癖。 希望其他人会介入一个明确的答案。 好像它不能很好地处理子进程,而且我假设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; } }); 

请注意,后台线程设置字段pid ,即UNIX进程ID。 destroy()将使用它来告诉操作系统要杀死哪个进程。

因为在调用destroy()时无法确保此后台线程已运行,我们可能会尝试在运行之前终止该进程或者我们可能会在设置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字段甚至没有标记为volatile,因此我们甚至可能看不到最近的值。

我有一个非常相似的问题,即使使用单个线程, destroy()无法正常工作也会出现问题。

 Process process = processBuilder(ForeverRunningMain.class).start() long endTime = System.currentTimeMillis() + TIMEOUT_MS; while (System.currentTimeMillis() < endTime) { sleep(50); } process.destroy(); 

如果TIMEOUT_MS太低,则该过程并不总是被破坏。 destroy() sleep()之前添加一个额外的sleep()修复它(即使我没有解释原因):

 Thread.sleep(300); process.destroy();