16

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

编辑:错误已报告给甲骨文,等待答复。随时分享有关该主题的任何知识/实验。

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

8 回答 8

6

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

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

于 2013-08-15T17:20:36.097 回答
5

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

由于部分原生平台只为标准输入输出流提供有限的缓冲区大小,未能及时写入子进程的输入流或读取输出流可能会导致子进程阻塞,甚至死锁。

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

如果您需要使用 Runtime.exec(),整篇文章是 IMO 值得一读的。

于 2013-08-07T22:14:33.093 回答
2

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

一个典型的解决方案:

p.destroy();
p.getInputStream().close();
p.getOutputStream().close();
p.getErrorStream().close();
于 2013-08-19T12:16:24.543 回答
2

这仅仅是因为在线程执行销毁调用之前,您的主程序终止并且所有关联的线程使启动的进程保持运行。要验证这一点,只需在销毁之后添加一个 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);

}

}

于 2013-08-19T12:00:49.057 回答
1

我相信根据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();
    }
}
}
于 2013-08-07T21:18:27.487 回答
0

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

我无法在 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();
        }

    }

}

无限循环.java

public class InfiniteLoop {
    public static final void main (String[] args) {
        while (true) ;
    }
}

我无法重现 JVM 终止后进程仍在运行的问题。但是,如果我在启动线程之后但在从主线程返回之前在主线程中添加一个较长的延迟,我确实会看到大约十几个正在运行的 Java 进程仍然存在(尽管它们在主程序终止时终止)。

更新:

我只是让它在它终止后留下大约 5 个进程运行。它并不总是发生。诡异的。我也想了解更多。我有一种预感,它与过快地破坏进程或某种竞争条件有关;也许java会分叉一些东西或者做一些事情来创建一个新的进程,如果调用太快/在错误的时间,destroy()就不会处理。

我发现了一个旧错误(但它没有标记为已解决),指出如果一个进程产生子进程,它们可能不会被 destroy() 杀死。bugs.sun.com/bugdatabase/view_bug.do?bug_id=4770092您使用的是什么版本的 JDK。

这是对类似问题的另一个参考:Java tool/method to force-kill a child process如果我只是给您的生活增添了困惑,我想道歉,我实际上并没有那么多使用 Process 并且不是熟悉的怪癖。希望其他人会介入并给出明确的答案。似乎它不能很好地处理子进程,我假设 java 分叉了一些东西。这就是我得到的。

于 2013-08-08T00:14:33.787 回答
0

在 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

UNIXProcessdestroy()中甚至有一条评论暗示了这一点,但只考虑在进程完成后调用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,所以我们甚至可能一直看不到最新的值。

于 2013-08-15T20:28:28.363 回答
0

我遇到了一个非常相似的问题,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();
于 2015-08-16T15:37:08.040 回答