Java Process.exec阻塞问题调查
发布时间:2020-10-14 11:39:03 阅读:293

Process原理

通过Java创建子进程,利用进程间的无名管道进行传输标准I/O流

管道原理

实现原理:内核借助环型队列机制,使用内核缓冲区实现。

管道的限制:

  1. 数据不能进程自己写,自己读
  2. 管道中数据不可以反复读取。一旦读走,管道中不再存在。
  3. 采用半双工通信方式,数据只能在单方向上流动。
  4. 只能在有公共祖先的进程间使用管道。

管道读写行为:

读管道:
  1. 管道有数据,read返回实际读到的字节数
  2. 管道无数据:
    • 管道写端全部被关闭时,read返回0。
    • 管道写端没有全部被关闭,read阻塞等待。
写管道:
  1. 管道读端全部被关闭,进程异常终止(只有在管道的读端存在时,向管道中写入数据才有意义。否则,向管道中写入数据的进程将收到内核传来的SIFPIPE信号, 应用程序可以处理该信号,也可以忽略(默认动作则是应用程序终止))
  2. 管道读端没有全部关闭
    • 管道已满,write阻塞等待
    • 管道未满,write将数据写入,并返回实际写入的字节数。

Process执行逻辑

  • 主进程中调用Runtime.exec会创建一个子进程,用于执行脚本。子进程创建后会和主进程分别独立运行。

  • 创建的子进程没有自己的终端或控制台。它的所有标准 io(即 stdin、stdout 和 stderr)操作都将通过三个流 (getOutputStream()、getInputStream() 和 getErrorStream()) 通过管道输入端重定向到父进程。父进程使用这些流来提供到子进程的输入和获得从子进程的输出。

  • 这时候子进程不断向主进程发生数据,而主进程调用Process.waitfor后已挂起。当前子进程和主进程之间的缓冲区塞满后,子进程不能继续写数据,然后会挂起。这样子进程等待主进程读取数据,主进程等待子进程结束发送EOF,两个进程相互等待,最终导致死锁。

/**
** This method blocks until input data is available, end of file is detected, or an exception is thrown.
*/
public int read(byte[] b, nt off, int len) throws IOException;

解决方法

  1. 在waitFor()之前,利用单独两个线程,分别处理process的getInputStream()和getErrorSteam(),防止缓冲区被撑满,导致阻塞;

缺点:标准输入流和标准输出流会堆在一块输出。方案二没有该问题

  1. 利用ProcessBuilder.redirectErrorStream()重定向错误流到输入流中

其他知识

  1. ProcessBuilder有一个inheritIO()方法 这个方法等同于
processBuilder.redirectError(Redirect.INHERIT);
processBuilder.redirectInput(Redirect.INHERIT);
processBuilder.redirectOutput(Redirect.INHERIT);

JDK中对Redirect.INHERIT描述

Indicates that subprocess I/O source or destination will be the same as those of the current process. This is the normal behavior of most operating system command interpreters (shells).

个人理解就是把子进程的标准输入流、标准输出流、标准错误流都重定向到当前进程(Java进程)
实际效果就是将子进程的流输出到Java的控制台中

发表评论
使用 Nuxt 3 构建 | 部署于 Kubernetes | 托管于 狗云
Copyright © 2020-2024 | 网站已续航 1530 天