Почему процесс зависает, если родитель не использует stdout/stderr в Java?
Я знаю, что если вы используете ProcessBuilder.start
в Java для запуска внешнего процесса вы должны использовать его stdout/stderr (например, см. здесь). В противном случае внешний процесс зависает при запуске.
Мой вопрос, почему это работает так. Я предполагаю, что JVM перенаправляет stdout / stderr выполненного процесса на каналы, и если каналы не имеют места, записи в блок каналов. Имеет ли это смысл?
Теперь мне интересно, почему Java так поступает. Каково обоснование этого дизайна?
2 ответа
Ява ничего не делает в этой области. Он просто использует службы ОС для создания каналов.
В этом отношении все Unix-подобные ОС и Windows ведут себя одинаково: между родителем и потомком создается канал с 4K. Когда этот канал заполнен (потому что одна сторона не читает), процесс записи блокируется.
Это стандарт с момента появления труб. Ява мало что может сделать.
Можно утверждать, что API-интерфейс процесса в Java неуклюж и не имеет хороших значений по умолчанию, таких как простое подключение дочерних потоков к тому же stdin/stdout, что и родительский, если разработчик не переопределит их чем-то конкретным.
Я думаю, что есть две причины для текущего API. Прежде всего, разработчики Java (то есть парни из Sun/Oracle) точно знают, как работает API процесса и что вам нужно делать. Они знают так много, что им не пришло в голову, что API может сбить с толку.
Вторая причина в том, что нет хорошего дефолта, который будет работать для большинства. Вы не можете действительно соединить stdin родителя и ребенка; если вы наберете что-то на консоли, к какому процессу должен идти ввод?
Точно так же, если вы подключите стандартный вывод, вывод куда-нибудь пойдет. Если у вас есть веб-приложение, возможно, нет консоли, или вывод может идти куда-то, где никто не будет этого ожидать.
Вы не можете даже выдать исключение, когда канал заполнен, так как это может произойти и во время нормальной работы.
Это объясняется в Javadoc процесса:
По умолчанию созданный подпроцесс не имеет собственного терминала или консоли. Все его стандартные операции ввода-вывода (т.е. операции stdin, stdout, stderr) будут перенаправлены в родительский процесс, где к ним можно получить доступ через потоки, полученные с помощью методов getOutputStream(), getInputStream() и getErrorStream(). Родительский процесс использует эти потоки для подачи входных данных и получения выходных данных из подпроцесса. Поскольку некоторые собственные платформы предоставляют ограниченный размер буфера только для стандартных входных и выходных потоков, невозможность оперативной записи входного потока или чтения выходного потока подпроцесса может привести к блокировке или даже взаимной блокировке подпроцесса.