当子进程仍然打开时,为啥 Java 进程会从 Gradle 挂起?

Posted

技术标签:

【中文标题】当子进程仍然打开时,为啥 Java 进程会从 Gradle 挂起?【英文标题】:Why does a Java process hang from Gradle when sub-process is still open?当子进程仍然打开时,为什么 Java 进程会从 Gradle 挂起? 【发布时间】:2014-03-22 01:18:49 【问题描述】:

如果一个在java中创建的进程创建了一个子进程,然后又返回了,JVM就会挂起,但是没有进程ID。

下面的示例应用程序(需要 Windows 和 Java 7)

import java.io.File;
import java.io.IOException;
import java.lang.ProcessBuilder.Redirect;
import java.nio.file.Files;

public class SubProcessHang 

    public static void main(String[] args) throws IOException, InterruptedException 
        ProcessBuilder builder = new ProcessBuilder("cmd", "/c", "start", "notepad.exe");
        File output = Files.createTempFile("output", "txt").toFile();
        builder.redirectError(Redirect.to(output));
        builder.redirectOutput(Redirect.to(output));
        Process process = builder.start();
        process.waitFor();
        int exitValue = process.exitValue();
        System.out.println("Process exit value:: " + exitValue);
        System.out.println("Output file length:: " + output.length());
        System.exit(exitValue);
    

当应用程序运行时,它会创建三个进程: java --> cmd --> 记事本 cmd 立即返回,java 调用 System.exit(0),这会杀死 java 进程。 但是记事本仍然存在,并且当从 gradle(或 eclipse 中)运行时,JVM 会一直徘徊,直到该进程消失,而不是返回它的返回值。

所以子进程仍然活着,但父进程已被部分杀死,但现在永远搁浅了。

重现这个的 build.gradle 脚本

apply plugin: 'java'
apply plugin: 'application'
mainClassName = "SubProcessHang"

执行 'gradle run' 并获得以下输出:

C:\HangDemo>gradlew run
:compileJava
:processResources UP-TO-DATE
:classes
:run
Process exit value:: 0
Output file length:: 0
> Building 75% > :run

我知道这一定与 java 进程的创建方式有关,但我不知道该怎么做。

除了获取正在运行的 java 进程的 ID 并在关闭挂钩中杀死所有子进程之外,我还能做什么?

【问题讨论】:

这看起来与我的 gradle 无关。您是否尝试过从命令行运行您的课程? 是的。它可以从命令行按预期工作(无论如何,正如我所料)。它返回退出状态,记事本仍在运行。问题是 gradle 是运行这里建模的 java 进程的自动化工具,并且永远不会返回。 【参考方案1】:

Process 的文档说

默认情况下,创建的子进程没有自己的终端或控制台。它的所有标准 I/O(即 stdin、stdout、stderr)操作都将被重定向到父进程,在那里可以通过使用 getOutputStream()、getInputStream() 和 getErrorStream() 方法获得的流来访问它们。父进程使用这些流向子进程提供输入并从子进程获取输出。由于部分原生平台只为标准输入输出流提供有限的缓冲区大小,未能及时写入子进程的输入流或读取输出流可能导致子进程阻塞,甚至死锁。

http://docs.oracle.com/javase/7/docs/api/java/lang/Process.html

也许您的进程正在创建标准输出或标准错误输出。尝试排空 InputStream 和 ErrorStream。

【讨论】:

我正在管道传输实际违规过程的输出流。那不是问题。在示例中,notepad.exe 没有输出。只是它还活着。 你执行的流程的流呢?在您的示例中,我说的是耗尽 p.getInputStream() 和 p.getErrorStream() 我已经澄清了这个例子以表明这不是问题所在。当这是一个问题时,通常 process.waitFor() 会挂起,并且 JVM 线程保持活动状态。这些都没有发生在这里。【参考方案2】:

我想说this answer 可能有助于获取子进程 ID 和 this one - 在 Windows 环境中杀死它们。

希望有帮助!

【讨论】:

所以问题在于java进程启动了启动记事本进程的cm​​d进程......但随后cmd进程消失了。这是java和记事本之间的唯一联系。所以你甚至不能杀死所有的子进程,因为它不再知道notepad.exe了。

以上是关于当子进程仍然打开时,为啥 Java 进程会从 Gradle 挂起?的主要内容,如果未能解决你的问题,请参考以下文章

Perl:当子/管道的文件句柄被别名时,关闭子进程失败

当文件在另一个进程中打开时,Inno Setup LoadStringFromFile 失败

为啥JAVA进程占用内存会超过Xmx设置

我的火狐浏览器为啥打不开左上角的Firefox了,鼠标右键也没反应,怎么回事

如何清理僵尸进程(转载)

僵尸进程