我有一个二进制linux可执行文件,它在stdout中输出一些字节。我从Java应用程序中消耗这些字节:
String[] cmd;
Process p = Runtime.getRuntime().exec(cmd);
InputStream is = p.getInputStream();
int r = is.read();
while(r != -1){
System.out.println(r);
r = is.read(); //1
}
但是经过一段时间的工作后,//1
永远被阻止了I / O(死锁)。我创建了线程转储并注意到了
"pool-2-thread-1@627" prio=5 tid=0xd nid=NA runnable
java.lang.Thread.State: RUNNABLE
at java.io.FileInputStream.readBytes(FileInputStream.java:-1)
at java.io.FileInputStream.read(FileInputStream.java:255)
at java.io.BufferedInputStream.read1(BufferedInputStream.java:284)
at java.io.BufferedInputStream.read(BufferedInputStream.java:345)
- locked <0x2c0> (a java.lang.UNIXProcess$ProcessPipeInputStream)
at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
它锁定了原生方法
private native int readBytes(byte[] var1, int var2, int var3) throws IOException;
这种方法永远被阻止的可能原因是什么?也许这是特定于平台的。我使用Ubuntu 16.04
。
答案 0 :(得分:1)
您的InputStream
正在等待进程 提供另一个字节,或关闭流(通过完成)。
这个过程可能不会做这些事情的一个原因是,它被写入stderr
,并在写入时阻止。
您的shell和Java使用的环境之间的环境可能存在差异,这意味着命令导致输出到stderr(例如,程序不在$PATH
;您没有足够的权限;你的CWD不是你所期望的等等。)
快速而又肮脏的方法来确定这是否是问题,是将stderr
重定向到stdout
- 使用ProcessBuilder.redirectErrorStream()
或使用2>&1
UNIX方面的事情。
如果就是这种情况,那么 是一种死锁形式--Java程序正在等待本机程序写入一个管道。本机程序正在等待Java程序从另一个程序读取。
处理它的干净方法,你可以选择长期使用,是使用getErrorStream()
并处理它的输出。这并非完全无关紧要,因为在使用阻塞读取的单线程程序中,您永远无法知道哪个流将具有数据。您可以在单独的线程中执行阻塞读取,也可以使用NIO以非阻塞方式处理两个输入。
顺便说一句 - 请注意,自Java 1.5以来,Java文档建议ProcessBuilder.start()
超过Runtime.exec()
。
答案 1 :(得分:0)
您不需要查看标准库的内部方法以获得答案。 The docs of InputStream.read()
部分指定了
如果没有可用的字节,因为流的结尾已经存在 到达时,返回值-1。此方法阻塞直到输入数据 可用,检测到流的末尾,或者是异常 抛出。
也许混淆的关键点是&#34;检测到流的末尾。&#34;这个不意味着现在没有更多的字节可供读取 - 这与方法阻止的规范一致,直到数据可用,并且在实践中它会很难解决在很多情况下。相反,当系统接收到某种信号时,将检测到流的末尾,该信号不再能够从其中获得字节。通常,这意味着流已在源端关闭,并且所有字节在目的端从其中排出。
在您的特定情况下,如果外部进程仍在运行,不会向其标准输出写入任何内容,但是关闭其标准输出,则Java(或本机使用者)将永远不会查看该流程的输出结束。