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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/18113941/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 22:28:55  来源:igfitidea点击:

Thread-launched running processes won't destroy (Java)

javamultithreadingprocessdestroy

提问by Bastien

Starting multiple threads and having each exec() then destroy() a running java process result in some of the process not being destroyed and still running after program exit. Here is some code that reproduce the issue. I noticed the more threads you start, the more processes stay alive. And the more sleep before destroy(), the less processes stay alive. (I used InfiniteLoop as an example. Any running process will do the trick.)

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

EDIT : Bug has been reported to Oracle, waiting for an answer. Feel free to share any knowledge/experiments on the subject.

编辑:错误已报告给 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();
}

回答by Ali B

I believe that according to link, a distinct process is spawned by the operating system in response to this call. This process has a lifetime independent of your Java program and threads within it so you would expect it to continue running after your program has exited. I just tried it on my machine and it appeared to work as expected:

我相信根据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();
    }
}
}

回答by Andrei Juan

If subprocesses write anything to stdout or stderr (intentionally or not), that could cause trouble:

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

"Because some native platforms only provide limited buffer size for standard input and output streams, failure to promptly write the input stream or read the output stream of the subprocess may cause the subprocess to block, and even deadlock."

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

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

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

The whole article is IMO worth reading if you need to use Runtime.exec().

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

回答by Jason C

This is not an answer; I am posting complete source for my own attempt at recreating this problem as per discussion in question comments.

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

I cannot reproduce this problem on Ubuntu 12.04; OpenJDK 6b_27 (however, see below).

我无法在 Ubuntu 12.04 上重现这个问题;OpenJDK 6b_27(但是,请参见下文)。

ProcessTest.java:

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

无限循环.java

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

I cannot reproduce the issue where processes remaining running after the JVM terminates. However, if I add a long delay in the main thread after starting the threads but before returning from main, I do see roughly a dozen running java processes that stick around (although they are terminated when the main program terminates).

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

Update:

更新:

I just had it leave about 5 processes running after it terminated. It doesn't always happen. Weird. I want to know more about this too. I have a hunch that it has something to do with destroying the process too quickly or some kind of race condition; maybe java forks something off or does something to create a new process that destroy() doesn't take care of if called too quickly / at the wrong time.

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

I found an old bug (but it is not mark resolved) stating that if a process spawns subprocesses they may not be killed by destroy(). bugs.sun.com/bugdatabase/view_bug.do?bug_id=4770092What version of the JDK are you using.

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

Here's another reference to what looks like a similar issue: Java tool/method to force-kill a child processAnd I want to apologize if I've only added confusion to your life, I don't actually use Process that much and am not familiar with the quirks. Hopefully somebody else will step in with a definitive answer. It seems like it doesn't handle subprocesses well, and I'm presuming java forks something off. That's all I got.

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

回答by Abhishek

Use a p.waitFor();before p.destroy();,

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

this will ensure the completion of the previous process. I think you p.destroy command gets invoked sooner than the exec()command performs the action. Therefore it becomes useless.

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

回答by dkatzel

There is a race condition between the time Runtime.exec kicks off a new threadto start a Process and when you tell that process to destroy itself.

在 Runtime.exec 启动一个新线程以启动 Process 和您告诉该进程销毁自身的时间之间存在竞争条件。

I'm on a linux machine so I will use the UNIXProcess.class file to illustrate.

我在一台 linux 机器上,所以我将使用 UNIXProcess.class 文件来说明。

Runtime.exec(...)will create a new ProcessBuilderand start it which on a unix machine creates a new UNIXProcessinstance. In the constructor of UNIXProcessthere is this block of code which actually executes the process in a background (forked) thread:

Runtime.exec(...)将创建ProcessBuilder一个新UNIXProcess实例并启动它,它在 unix 机器上创建一个新实例。在构造函数中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;
    }
});

Notice that the background thread sets the field pidwhich is the UNIX process id. This will be used by destroy()to tell the OS which process to kill.

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

Because there is no way to make sure that this background thread has run when destroy()is called, we may try to kill the process before it has run OR we may try to kill the process before pid field has been set; pid is uninitialized and therefore is 0. So I think calling destroy too early will do the equivalent of a kill -9 0

因为没有办法确保这个后台线程在destroy()被调用时已经运行,我们可能会尝试在它运行之前杀死该进程,或者我们可能会在设置 pid 字段之前尝试杀死该进程;pid 未初始化,因此为 0。所以我认为过早调用 destroy 相当于kill -9 0

There is even a comment in the UNIXProcess destroy()that alludes to this but only considers calling destroy after the process has already finished, not before it has started:

在 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.

The pid field is not even marked as volatile so we may not even see the most recent value all the time.

pid 字段甚至没有标记为 volatile,因此我们甚至可能不会一直看到最新的值。

回答by Raji

This is simply because before the threads execute the destroy call, your main program terminates and all the associated threads leaving the started processes running. To verify this, simply add a System.out call after the destroy and you will find it is not executed. To overcome this add a Thread.sleep at the end of your main method and you will not have the orphaned processes. The below does not leave any process running.

这仅仅是因为在线程执行 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);

}

}

}

回答by jtahlborn

You should close the input/output/error streams to the process. we saw some issues in the past where the forked process was not completing properly due to those streams not being closed (even if they weren't being used).

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

回答by Mifeet

I had a very similar issue and the problem with destroy()not working was manifesting even with a single thread.

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

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

The process was not always destroyed if TIMEOUT_MSwas too low. Adding an additional sleep()before destroy()fixed it (even though I don't have an explanation why):

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

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