java - 当另一个 java 程序在进程中启动时,Java jar 执行卡住了
问题描述
这是我遇到的一个非常不寻常的问题,我希望有人可能对此有所了解。我在 macOS Mojave (10.14.6) 上,使用 Amazon 的 JRE Corretto-11.0.9.12.1 (build 11.0.9.1+12-LTS)
我有一个我编写的程序,它是一个脚本引擎,我们用它来进行日常的批量数据处理。它需要一个 xml“脚本”文件,其中包含要执行的处理方向和输入文件列表作为参数。从现在开始,我将把它称为“引擎”。这个引擎是我们大部分自动化的支柱。它在处理时显示一个进度条,让用户知道它正在工作。
有 2 个程序使用此引擎:
- 一个是用 Swing 编写的瘦 UI,我们用它来手动处理数据;它从用户输入生成一个 xml 文件,并将其与输入文件一起传递,并在单独的进程中启动引擎;UI 本身不处理任何数据。
- 另一个在我们的文件服务器上监视一个文件夹,并每天在其中创建一个文件夹时处理来自我们客户的传入数据,以便我们可以将数据提取到我们的数据库中。我称其为“进口商”。
最近,出现了一个问题,即引擎在处理时卡住了。旧版本的引擎没有这个问题,我试图找出导致这种情况发生的确切变化,但是当我一直在尝试这样做时,我们的 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
以这种方式启动时,有一个特定的操作会导致引擎挂起。但是如果我接受命令并直接从命令行启动它,引擎运行得很好!这使得很难确定问题的确切位置。是在引擎中,还是在其他程序中?我花了几天时间寻找答案,却一无所获。更令人沮丧的是,这个问题在之前很长一段时间内使用上面的确切代码完美运行时似乎突然出现了。
引擎挂起的操作根据文件名将文件分类到文件夹中。当我在运行时查看我的活动监视器时,它根本不会占用我的资源,磁盘空间也不是问题。这不是文件权限问题,因为引擎一直在创建文件和文件夹,并且每一步都在创建文件和文件夹,直到它挂起的步骤。正如我所说,如果我直接从命令行运行命令,它会毫无问题地创建文件夹并对文件进行排序,这让我非常困惑。
导入器和 UI 在站点本地运行,但引擎 jar 文件存在于我们的文件服务器上,因此每个站点都可以访问它,而无需在每次有更新时单独下载它。起初我认为问题可能在于它是通过网络访问的事实,但即使我在我的开发机器上使用引擎的本地副本也会出现问题,所以我已经排除了这一点。我还排除了它是 JRE,即使我们最近切换到它,因为旧版本的引擎仍然按预期运行。我对此束手无策,所以我希望有人能提供帮助!
解决方案
当然,您的“引擎”程序可能会挂起 ;-) 当然可能有任何原因,但它肯定会挂起你不阅读它的输出,并且以正确的方式:
父进程需要读取子进程的标准输出和标准错误流,因为子进程确实在这两个通道中的任何一个通道上生成了大量的输出。这必须在两个单独的后台线程中完成。如果父进程没有读取子进程的输出,那么一旦进程之间的(小)缓冲区被填满,子进程就会阻塞。
线程应该在子进程启动后立即启动,并且在父进程调用之前启动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 中引入的并发框架可能有更复杂的方法来处理后台线程,但是这个基本代码给出了这个想法。
推荐阅读
- python - 使用 Py2app 识别包中的嵌套文件
- javascript - 使用空格在 html textarea 中的单词之间保持相同的间隙
- javascript - 获取javascript类中静态方法的值
- javascript - 如何导入 sanitize.js 以响应应用程序功能/类未找到
- scala - 如何使用一些正则表达式在 scala 中删除多个列?
- vue.js - quasar 无法建立资产文件夹
- javascript - 如何将变量从控制器传递到视图并在 Laravel 的 Vue.js 上使用它?
- python - 默认情况下获取一个实例来评估某些东西
- flutter-web - 如何检测flutter web中的鼠标滚轮滚动?
- javascript - 我如何编写一个程序,用于在数据库的帮助下自动单击复选框并显示在引导模式页面中