分析两个 JVM 服务崩溃的 CASE

Table Of Contents

线上有个古老的核心服务总是跑着跑着就挂掉,JVM 进程直接异常退出,不定期而且总是发生在一台服务器上,不仅业务崩溃,开发也崩溃。

# buffer overflow detected java terminated

*** buffer overflow detected ***: /usr/local/java8/jre/bin/java terminated
======= Backtrace: =========
/lib64/libc.so.6(__fortify_fail+0x37)[0x7f02bf900b67]
/lib64/libc.so.6(+0x115ce2)[0x7f02bf8fece2]
/lib64/libc.so.6(+0x117ac7)[0x7f02bf900ac7]
/usr/lib/libmsc64.so(+0x7a41d)[0x7f00537fa41d]
/usr/lib/libmsc64.so(+0x4a662)[0x7f00537ca662]
/lib64/libpthread.so.0(+0x7dd5)[0x7f02bffd9dd5]
/lib64/libc.so.6(clone+0x6d)[0x7f02bf8e702d]
======= Memory map: ========
5c0000000-7c1c60000 rw-p 00000000 00:00 0
7c1c60000-800000000 ---p 00000000 00:00 0
5568c3aca000-5568c3acb000 r-xp 00000000 fd:01 83886232                   /opt/jdk/jdk1.8.0_261/jre/bin/java
5568c3cca000-5568c3ccb000 r--p 00000000 fd:01 83886232                   /opt/jdk/software/jdk1.8.0_261/jre/bin/java
5568c3ccb000-5568c3ccc000 rw-p 00001000 fd:01 83886232                   /opt/jdk/software/jdk1.8.0_261/jre/bin/java

盯了几次这个问题发现,java terminated 之前打的日志总是有一个语音文件转换方法在执行。

这个方法先下载 OSS 上的语音文件到本地,然后调用 ffmpeg 进行格式转换。

一开始怀疑是 ffmpeg 导致的问题,结合 ffmpeg 和 buffer overflow 关键字,有看到 GCC 的编译选项还会对缓冲区溢出做检查,排查不下去了。

后来通过对这个方法进行细致化的打日志,发现崩溃的时候,日志总是执行到了下载文件的地方。

我们借助 GPT 找到了其中依赖的这样一段代码,可能有关系。

int readed;
while ((readed = input.read()) != -1) {
    out.write(readed);
}

线上的语音文件大小有几十兆,逐字节的读写文件,不仅会导致性能问题,也有可能引起 buffer overflow 的问题,应该使用缓冲 Buffer 读写优化。

后续优化是,将这堆操作第三方库的逻辑拆到单独的服务部署。

# core dump

这个场景系统直接生成了 core dump 文件,在服务器上使用 gdb java core.68640 分析如下

将汇编代码发给 GPT 分析得出了文件描述符相关的问题

联想到之前迁移测试环境发现的服务文件描述符一直在上涨,怀疑可能是文件泄露。

用 sonar 扫描了下项目代码,发现几十处没有关闭流的问题。

修改后确实没有再发现这个问题了。

# 总结

这两个问题其实归根结底都是基础的代码规范问题,没有质检、没有卡关,最终演变成了疑难杂症。

至于上面说为什么问题总是发生在一台服务器上,也是一个临时解决方案导致的,系统版本问题导致某个业务的接口流量都打到了这台服务器上。

前面写的多随心所欲,后人需要付出的智慧就越多。