Java 当另一个 java 程序在进程中启动时,jar 执行卡住

Java jar execution stuck when launched in a process by another java program

这是我遇到的一个非常不寻常的问题,我希望有人能对此有所了解。我在 macOS Mojave (10.14.6) 上,使用亚马逊的 JRE Corretto-11.0.9.12.1 (build 11.0.9.1+12-LTS)

我有一个我编写的程序,它是我们用来进行日常批量数据处理的脚本引擎。它需要一个 xml “脚本”文件,其中包含要执行的处理指令和输入文件列表作为参数。从现在开始,我将把它称为“引擎”。该引擎是我们大部分自动化的 backbone。它在处理时显示进度条,让用户知道它正在工作。

有 2 个程序使用此引擎:

最近遇到了一个问题,引擎在处理的时候卡住了。旧版本的引擎没有这个问题,我试图弄清楚到底是什么改变导致了这种情况发生,但是当我一直在尝试这样做时,我们的 UI 和导入程序已经使用和旧版本的引擎。新版引擎中有一些新特性需要用到,但是要解决这个问题才能用

使用该引擎的程序在进程中启动它,然后在继续之前等待结果:

// example command generated from external program
String commandString = "java -jar engine.jar script.xml input_file1.txt input_file2.txt input_file3.txt";
String[] command = {"bash", "-c", commandString};
// I can grab the command from here for debugging
System.out.println(Arrays.toString(command));
ProcessBuilder pb = new ProcessBuilder(command);
// wait for the process to complete before continuing
Process p = pb.start();
p.waitFor();
int result = p.exitValue();
try (BufferedReader e = new BufferedReader(new InputStreamReader(proc.getErrorStream()));
    BufferedReader i = new BufferedReader(new InputStreamReader(proc.getInputStream()))) {
    String line;
    while ((line = e.readLine()) != null) {
        System.out.println(line);
    }
    while ((line = i.readLine()) != null) {
        System.out.println(line);
    }
}
p.destroy();
// do other stuff

以这种方式启动时,有一个特定的操作会导致引擎挂起。但是如果我接受命令并直接从命令行启动它,引擎 运行 就好了!这使得很难确定问题的确切位置;它是在引擎中,还是在其他程序中?我花了几天时间寻找答案,但一无所获。更令人沮丧的是,这个问题似乎无处不在,而它之前使用上面的确切代码完美运行了很长时间。

引擎挂起的操作根据文件名将文件分类到文件夹中。当我在 运行 时观看 activity 监视器时,它根本不会占用我的资源,磁盘 space 也不是问题。这不是文件权限问题,因为引擎一直在创建文件和文件夹,并且在导致它挂起的步骤的每个步骤中。正如我所说,如果我直接从命令行 运行 命令,它会毫无问题地创建文件夹并对文件进行排序,这让我非常困惑。

导入器和 UI 运行 在本地站点上,但引擎 jar 文件位于我们的文件服务器上,因此每个站点都可以访问它,而无需每次都单独下载它是一个更新。起初我认为问题可能在于它正在通过网络访问,但即使我在我的开发机器上使用引擎的本地副本时也会出现问题,所以我已经排除了这种可能性。我还排除了它是 JRE,即使我们最近切换到它,因为旧版本的引擎仍然按预期运行。我对此束手无策,所以我希望有人能提供帮助!

您的 'engine' 程序可能会挂起当然可能有任何原因 ;-) 但肯定 挂起你没有阅读它的输出,并且正确的方式:

父进程需要读取 标准输出标准错误流子进程,因为子进程确实会在这两个通道中的任何一个上生成任何大量的输出。此 必须 在两个单独的后台线程中完成。如果父进程不读取子进程的输出,那么一旦进程之间的(小)缓冲区被填满,子进程就会阻塞。

线程应在子进程启动后立即启动,并且在父进程调用之前启动 process.waitFor()

最简单的方法如下:

    Process process = processBuilder.start();
    InputStream stdout = process.getInputStream();
    InputStream stderr = process.getErrorStream();

    Thread stdoutThread = new Thread() {
        @Override
        public void run() {
            // read stdout here, e.g.
            try {
                int c;
                while (-1 != (c = stdout.read())) {
                    // do whatever with c
                }
            } catch (IOException ioex) {
                // ...
            }
        }
    };

    Thread stderrThread = new Thread() {
        // ... same as for stdout
    };

    stdoutThread.start();
    stderrThread.start();
}

只有在两个线程都启动后,您才可以等待子进程并加入线程:

    int exitValue = process.waitFor();
    stdoutThread.join();
    stderrThread.join();

可能有更复杂的方法使用 Java 5 中引入的并发框架来处理后台线程,但是这个基本代码给出了想法。