如果父级不使用 Java 中的 stdout/stderr,为什么 process 会挂起?

2022-09-04 19:39:46

我知道,如果你在Java中使用一个外部进程来启动一个外部进程,你必须使用它的stdout/stderr(例如,见这里)。否则,外部进程在启动时挂起。ProcessBuilder.start

我的问题是为什么它以这种方式工作。我的猜测是,JVM会将已执行进程的stdout/stderr重定向到管道,如果管道没有空间,则写入管道块。这有意义吗?

现在我想知道为什么Java会这样做。这种设计背后的基本原理是什么?


答案 1

Java在这方面不做任何事情。它只使用操作系统服务来创建管道。

所有像操作系统和Windows这样的Unix在这方面的行为都是一样的:在父级和子级之间创建一个4K的管道。当该管道已满时(因为一端未读取),写入过程将阻塞。

这是自管道问世以来的标准。Java能做的不多。

你可以争辩说,Java中的流程API很笨拙,并且没有很好的默认值,比如简单地将子流连接到与父级相同的stdin/stdout,除非开发人员用特定的东西覆盖它们。

我认为目前的API有两个原因。首先,Java开发人员(即Sun /Oracle的人员)确切地知道流程API的工作原理以及您需要做什么。他们知道得太多了,以至于他们没有想到API可能会令人困惑。

第二个原因是,没有好的默认值可以对大多数人起作用。你不能真正连接父母和孩子的stdin;如果您在控制台上键入内容,则输入应转到哪个进程?

同样,如果连接 stdout,输出将转到某个位置。如果你有一个 Web 应用,则可能没有控制台,或者输出可能会转到没人期望它的地方。

当管道已满时,您甚至不能引发异常,因为在正常操作期间也可能发生这种情况。


答案 2

它在 Process 的 javadoc 中进行了解释:

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


推荐