分析 Runtime.getRuntime() 执行阻塞原因

news2024/11/24 9:25:01

1、起因

        线上系统通过 git 命令执行的方式获取远程仓库分支,一直运行正常的接口,突然出现超时,接口无法响应,分析验证发现只有个别仓库获取分支会出现这种情况,其他都还是可以正常获取到分支结果信息。

2、分析异常原因

        分析接口代码并没有发现明显异常,为什么有的仓库会出现阻塞卡死的问题呢? 登陆到服务器上查看进程,发现关于这个仓库的 git 命令进行全部都卡死了。

a、执行 jstack pid 看下线程运行情况

http-nio-7629-exec-9" #250 daemon prio=5 os_prio=0 tid=0x00007f047f37c000 nid=0x5f0d0 runnable [0x00007f03cb411000]
   java.lang.Thread.State: RUNNABLE
    at java.io.FileInputStream.readBytes(Native Method)
    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 <0x00000006d499c890> (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)
    - locked <0x00000006d499c918> (a java.io.InputStreamReader)
    at java.io.InputStreamReader.read(InputStreamReader.java:184)
    at java.io.BufferedReader.fill(BufferedReader.java:161)
    at java.io.BufferedReader.readLine(BufferedReader.java:324)
    - locked <0x00000006d499c918> (a java.io.InputStreamReader)
    at java.io.BufferedReader.readLine(BufferedReader.java:389)
    at com.utils.GitUtils.actualExcute(GitUtils.java:106)
    at com.utils.GitUtils.getRemoteBranchesByCmd(GitUtils.java:50)

关于 git 命令执行的线程全部都是 runnable, 状态没有问题,但是流读取却都出现了 locked ,java.io.BufferedInputStream.read locked <0x00000006d499c890>

b、分析 UNIXProcess#ProcessPipeInputStream 为什么出现 locked 

分析源码实现,发现锁 closeLock 在 processExited、close 方法执行时会加锁

    /**
     * A buffered input stream for a subprocess pipe file descriptor
     * that allows the underlying file descriptor to be reclaimed when
     * the process exits, via the processExited hook.
     *
     * This is tricky because we do not want the user-level InputStream to be
     * closed until the user invokes close(), and we need to continue to be
     * able to read any buffered data lingering in the OS pipe buffer.
     */
    private static class ProcessPipeInputStream extends BufferedInputStream {
        private final Object closeLock = new Object();

        ProcessPipeInputStream(int fd) {
            super(new FileInputStream(newFileDescriptor(fd)));
        }
        private static byte[] drainInputStream(InputStream in)
                throws IOException {
            int n = 0;
            int j;
            byte[] a = null;
            while ((j = in.available()) > 0) {
                a = (a == null) ? new byte[j] : Arrays.copyOf(a, n + j);
                n += in.read(a, n, j);
            }
            return (a == null || n == a.length) ? a : Arrays.copyOf(a, n);
        }

        /** Called by the process reaper thread when the process exits. */
        synchronized void processExited() {
            synchronized (closeLock) {
                try {
                    InputStream in = this.in;
                    // this stream is closed if and only if: in == null
                    if (in != null) {
                        byte[] stragglers = drainInputStream(in);
                        in.close();
                        this.in = (stragglers == null) ?
                            ProcessBuilder.NullInputStream.INSTANCE :
                            new ByteArrayInputStream(stragglers);
                    }
                } catch (IOException ignored) {}
            }
        }

        @Override
        public void close() throws IOException {
            // BufferedInputStream#close() is not synchronized unlike most other
            // methods. Synchronizing helps avoid race with processExited().
            synchronized (closeLock) {
                super.close();
            }
        }
    }
c、本地复现 
public static void main(String[] args) throws InterruptedException {
        Runtime runtime = Runtime.getRuntime();
        try {
            Process process = runtime.exec("yes \"This is a normal log message\"  && yes \"This is a normal log message\" > &2");
            BufferedReader stdoutReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
            BufferedReader stderrReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
            String line;

            System.out.println("ERROR");
            while ((line = stderrReader.readLine()) != null) {
                System.out.println(line);
            }
            System.out.println("OUTPUT");
            while ((line = stdoutReader.readLine()) != null) {
                System.out.println(line);
            }
            int exitVal = process.waitFor();
            System.out.println("process exit value is " + exitVal);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

日志输出

Connected to the target VM, address: '127.0.0.1:58960', transport: 'socket'
ERROR

猜测是流读取导致的阻塞,回到开始看下  Process 的源码,发现文档说明中表示 Runtime.exec()创建的子进程公用父进程的流,父进程的stream buffer可能被打满导致子进程阻塞,从而永远无法返回。

By default, the created process does not have its own terminal or console. All its standard I/O (i.e. stdin, stdout, stderr) operations will be redirected to the parent process, where they can be accessed via the streams obtained using the methods getOutputStream(), getInputStream(), and getErrorStream(). The I/O streams of characters and lines can be written and read using the methods outputWriter(), outputWriter(Charset)}, inputReader(), inputReader(Charset), errorReader(), and errorReader(Charset). The parent process uses these streams to feed input to and get output from the process. Because some native platforms only provide limited buffer size for standard input and output streams, failure to promptly write the input stream or read the output stream of the process may cause the process to block, or even deadlock.

当使用 ProcessBuilder 或 Runtime.exec() 在 Java 中启动一个子进程时,子进程的标准输入(stdin)、标准输出(stdout)和标准错误(stderr)流会被重定向到父进程。父进程可以通过 getOutputStream()getInputStream() 和 getErrorStream() 方法访问这些流。由于某些本地平台(操作系统)对标准输入和输出流的缓冲区大小有限,如果父进程没有及时处理这些流(即没有及时读取子进程的输出或写入子进程的输入),可能会导致子进程阻塞,甚至导致死锁。这是因为子进程会等待缓冲区中的数据被读取或写入,从而继续执行。

d、产生阻塞或死锁的原因

输出缓冲区满:如果子进程产生了大量的输出(stdout 或 stderr),而父进程没有及时读取这些输出,缓冲区会被填满。当缓冲区满时,子进程将无法继续写入数据并因此阻塞,等待缓冲区有空闲空间。

输入缓冲区空:如果子进程需要从标准输入(stdin)读取数据,而父进程没有及时提供输入,子进程将阻塞,等待输入数据。

分析代码实现发现我们的代码是同步读取error 和正常 input  流,而且是先读取的 error ,按住上述说明,此时如果没有error日志,子进程将阻塞,等待输入数据。

调整代码实现,先读取正常流再读取error流,程序可以正常运行了。

3、总结

因为错误的流读取方式导致线程阻塞,虽然调整成先读取正常 input 流可以正常运行了,但是也存在全部输入为异常流,正常流无法读取的情况,所以最好的方式还是异步同时读取俩种流

解决方法

  • 为每个 I/O 流创建单独的线程,以确保及时读取子进程的输出和错误流,并及时写入子进程的输入流。
  • 如果不需要处理子进程的输出或错误流,可以使用 Redirect.INHERIT 让子进程继承父进程的 I/O 流。
  • 确保在读取和写入流时使用合适的缓冲区大小,以提高 I/O 操作的效率。这种不太靠谱因为大小本身就不可控

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2059006.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

音频分割怎么弄?手把手教会你实用的音频分割技巧

在巴黎的浪漫街头&#xff0c;打卡地标的方式已经达到了next level&#xff01;而今&#xff0c;想让这份记忆更加生动&#xff0c;不再只有照片与视频&#xff0c;更有音频的加入~ 想象一下&#xff0c;倘若用音频分割免费版工具来为这份旅行日志添上独一无二的音符&#xff…

Coze开发工作流

工作流可以理解是工作流程&#xff0c;就像流程审批的节点&#xff0c;它允许用户处理逻辑复杂且有较高稳定性要求的任务流。通过使用扣子提供的大量灵活可组合的节点&#xff0c;比如大语言模型 LLM、自定义代码、判断逻辑等&#xff0c;用户可以快速搭建工作流&#xff0c;无…

儿童乘坐火车高铁,忘带户口本了该怎么办?儿童临时身份证怎么办理?12306可申请儿童临时身份证明

儿童乘火车&#xff0c;没有携带户口本怎么办&#xff1f; 儿童乘坐火车/高铁时&#xff0c;家长需要携带儿童的有效身份证件才能乘车&#xff0c;如果到车站后发现忘记带户口本了&#xff0c;无法乘车怎么办&#xff1f;此时不要慌张&#xff0c;铁路部门已经为我们解决了这个…

界面控件DevExpress WinForms中文教程:Data Grid(数据网格)简介(二)

DevExpress WinForms Data Grid是一个高性能的UI组件&#xff0c;由DirectX渲染引擎提供支持。数据网格(GridControl)提供了一个灵活的基于视图的体系结构&#xff0c;包括许多数据塑造和UI自定义特性&#xff0c;数据网格可以显示和编辑来自任何大小和复杂数据源的数据。 P.S&…

详细扒一扒css的背景渐变(通俗易懂)

前言&#xff1a; CSS 渐变使您可以显示两种或多种指定颜色之间的平滑过渡。 CSS 定义了两种渐变类型&#xff1a; 线性渐变&#xff08;向下/向上/向左/向右/对角线&#xff09;径向渐变&#xff08;由其中心定义&#xff09; 下面来详细看看吧~ &#x1f308;&#x1f308;文…

【2.10】回溯算法-解黄金矿工问题

一、题目 你要开发一座金矿&#xff0c;地质勘测学家已经探明了这座金矿中的资源分布&#xff0c;并用大小为 m * n 的网格grid 进行了标注。每个单元格中的整数就表示这一单元格中的黄金数量&#xff1b;如果该单元格是空的&#xff0c;那么就是 0。 为了使收益最大化&#x…

代码随想录 刷题记录-12 回溯(1) 基本理论

什么是回溯法 回溯法也可以叫做回溯搜索法&#xff0c;它是一种搜索的方式。 回溯法的效率 虽然回溯法很难&#xff0c;很不好理解&#xff0c;但是回溯法并不是什么高效的算法。 因为回溯的本质是穷举&#xff0c;穷举所有可能&#xff0c;然后选出我们想要的答案&#xff…

【图文并茂】ant design pro 如何对接后端个人信息接口

上一节我们有讲到如何对接登录接口的 【图文并茂】ant design pro 如何对接登录接口 仅仅能登录是最基本的&#xff0c;但是我们要进入后台还是需要另一个接口。 这个接口有两个作用&#xff1a; 来获取当前登录账号的信息&#xff0c;比如头像&#xff0c;用户名&#xff0…

SAP Lock Object锁机制

一、锁机制 SAP LUW要求数据库对象的锁定在SAP LUW结束释放&#xff0c;并且该数据库锁要求对所有SAP程序可见。SAP提供了一个逻辑数据锁定机制&#xff0c;该机制基于系统特定的锁定服务应用服务器中的中心锁定表&#xff08;即将加锁的信息记入数据库表&#xff09;。一个AB…

文档翻译软件哪个好用?后悔没早发现这5款

在学术研究的道路上&#xff0c;英文文献翻译无疑是一项挑战重重的任务&#xff01; 作为一名经常与英文文献打交道的学者&#xff0c;我一直在寻找能够简化这一过程的工具。最近&#xff0c;我发现了一些英文文献翻译在线免费工具&#xff0c;它们提供了文档翻译的功能&#…

IC rankIC

IC IC衡量的是预测值和实际值之间的相关系数 计算公式为&#xff1a;IC Pearson(R(predicted),R(actual)) 取值范围&#xff1a;[-1, 1]&#xff0c;其中1表示完全相关&#xff0c;也就是预测值和实际值完全一样。0表示完全不相关&#xff0c;-1表示&#xff0c;反向相关 ra…

Catf1ag CTF Web(八)

前言 Catf1agCTF 是一个面向所有CTF&#xff08;Capture The Flag&#xff09;爱好者的综合训练平台&#xff0c;尤其适合新手学习和提升技能 。该平台由catf1ag团队打造&#xff0c;拥有超过200个原创题目&#xff0c;题目设计注重知识点的掌握&#xff0c;旨在帮助新手掌握C…

「Qt Widget中文示例指南」如何实现一个旋转框(二)

Qt 是目前最先进、最完整的跨平台C开发工具。它不仅完全实现了一次编写&#xff0c;所有平台无差别运行&#xff0c;更提供了几乎所有开发过程中需要用到的工具。如今&#xff0c;Qt已被运用于超过70个行业、数千家企业&#xff0c;支持数百万设备及应用。 旋转框示例展示了如…

别选错了!一篇文章讲清Midjourney 和 Stable Diffusion的区别

24年无疑标志着AI时代的崭新篇章&#xff0c;各类AI软件如同春日里迅速生长的竹笋&#xff0c;层出不穷&#xff0c;其功能之丰富令人目不暇接&#xff0c;竞相在各自的领域内抢占前沿阵地。 在众多 AI 绘画工具中&#xff0c;Midjourney 和 Stable Diffusion 是最为人熟知的两…

大数据背景下基于Python的牛油果销售数据可视化分析

注&#xff1a;源码在最后&#xff0c;只是一次实验记录&#xff0c;不合理的地方自行修改。 一 研究背景及意义 21世纪以来&#xff0c;随着科学技术的进步&#xff0c;人们的生活水平也随之大幅提升提高。在科技和经济快速发展下&#xff0c;全球已经进入了大数据时代。大数…

Stable Diffusion 适合亚洲人的摄影级画质LEOSAM‘s HelloWorld XL 大模型V6版本来啦!

前言 LEOSAM’s HelloWorld XL大模型早在SD1.5时代&#xff0c;就曾经被多次推荐&#xff0c;该作者LEOSAM于4月20日发布了其最新LEOSAM’s HelloWorld XL V6版本大模型。该模型在摄影、光影等方面表现非常优秀。HelloWorld XL V6版本是在V5版本的基础上迭代改进的。根据作者LE…

【JAVA入门】Day23 - 查找算法

【JAVA入门】Day23 - 查找算法 文章目录 【JAVA入门】Day23 - 查找算法一、基本查找二、二分查找 / 折半查找三、分块查找 查找算法我们常用的有&#xff1a; 基本查找二分查找 / 折半查找分块查找插值查找斐波那契查找树表查找哈希查找 这里我们着重讲解前三种&#xff0c;其…

计算机Java项目|基于SpringBoot的精简博客系统的设计与实现

作者主页&#xff1a;编程指南针 作者简介&#xff1a;Java领域优质创作者、CSDN博客专家 、CSDN内容合伙人、掘金特邀作者、阿里云博客专家、51CTO特邀作者、多年架构师设计经验、多年校企合作经验&#xff0c;被多个学校常年聘为校外企业导师&#xff0c;指导学生毕业设计并参…

第47课 Scratch入门篇:水果忍者

水果忍者 故事背景: 水果忍者是一款传统的非常好玩的游戏,我们通过鼠标控制水果刀,把弹出的水果切掉,如果切到地雷则扣分,这款游戏非常好玩,现在我们现在通过Scratch 把它做出来,! 程序原理: 这款游戏难点就是水果的抛起和下降,由于角色是从下往上走,也就是 Y 坐标…

ip归属地换地方了会自动更新吗

在这个数字化时代&#xff0c;互联网已成为我们生活、工作和学习中不可或缺的一部分。而每一个连接互联网的设备&#xff0c;都会通过其IP地址与外界进行通信。IP地址&#xff0c;这个看似简单的数字组合&#xff0c;实则承载着设备位置、网络身份等重要信息。随着人们移动性的…