记录一次线上OOM情况排查过程

7,750 阅读5分钟

现象 

某天凌晨,手机突然告警,线上某台机子内存使用率超过90%,当时以为是有定时任务在跑,再加上夜已深了,没有去排查具体原因。第二天早上有发布,内存降下来了,白天就没有去调查这个问题。等到傍晚高峰期的时候,又接到内存调用超过90%的告警,并且持续一段时间后,线上的某台机子挂了。

现场保存 

当时一台机子挂掉后,马上重启了挂掉的机子,并且把另一台机子的内存信息dump下来。

jmap -dump:format=b,file=文件名 [pid] 

 在dump过程中遇到小插曲,无法dump下来。


这种情况是因为非当前线程用户导致,在命令前面加上sudo -u 用户即可

同时保存了线程信息 

sudo -u jetty jstack pid > /tmp/jstack2018-04-18.txt

问题定位

查看服务器系统日志

cat /var/log/messages


上图看到 Killed process 7364, UID 502, (java) total-vm:10511252kB, anon-rss:7489308kB  java内存使用了7G多。ps:上一行的java进程的2627813 和 1872416都是页数,每页4K。

可以发现容器内存使用因为超过系统内存被kill掉了。

使用mat分析具体内存泄漏问题。

由于本人对mat工具使用的还不熟练,看到分布图后,total一共才103.6MB,以为堆内内存没有问题,便往堆外内存溢出方向去考虑。陆陆续续的检查了代码中的ThreadLocal的使用,对象也有及时清理,检查了线上线程的数量,都没有发现明显问题。

转载一篇线程过多导致的堆外内存溢出文章:线程数过多导致堆外内存溢出

继续使用mat分析,使用Leak Suspects功能,发现内存中有大量的TrueTypeFont对象

正好想到在该应用中,有使用到字体画图,发送图片的功能。于是乎查询一下OOM当时请求情况,发现确实有大量的画图的请求,并且发现某个请求中同时画600多张画。

问题重现

接着,在公司的测试环境下模拟了下请求,果然内存使用率由60多一下子升到了80多。

回头检查了一下画图的代码,发现在处理字体的地方确实有问题。

该方法的逻辑为加载服务器上的某个字体文件,使用画笔画一张二维码,然后保存到服务器的临时目录下。

这里在加载字体的时候没做好处理,导致每次过来一个画的任务就会加载一次字体文件,内存中创建一个字体对象,而我们服务器上的字体文件大小约有16M,也就相当于每次画一幅画就需要加载16M的内存大小。

分析原因 

由于当时对mat不熟悉,还在怀疑是不是Font操作了堆外空间导致的。于是乎准备测试一下堆外空间。

排除堆外空间溢出可能性

我们服务器的配置为8G内存,jvm配置为 -Xmx4428m -Xms4428m -Xmn2767m,堆外空间如果不限制的话,会和jvm使用差不多。这时候,我们限定堆外空间大小,比如我们指定堆外空间比如说只有100M -XX:MaxDirectMemorySize=100m 。

如果说Font使用的是堆外空间,那么堆外空间就会很快到达100M,并且进行Full GC,阻塞所有请求,Stop The World。这时候,只要Full GC清理及时,后面阻塞请求继续进来,继续满100M,继续Full GC。这样,内存使用率永远也不会到8G,就不会出现被系统kill掉的情况了。

但是在测试过程中,发现内存使用率还是在一路飙升,最终还是被kill了。所以后来只能再次怀疑是堆内内存溢出。用同样的策略,将堆内内存设置为1G(100M应用起不来了),发现内存没有继续往上升,并且查看了下gc日志,一直在进行Fulll GC,从这大致可以看出使用的是堆内的内存。

由此可以看出,当时高峰期,大量画图请求进来,导致大量的大对象加载进内存,最终导致jvm内存超过了系统内存,容器被系统kill掉。

解决问题

将Font改为单例模式,内存中只存在一份。


在公司测试环境再次试了下,内存使用率没有变化。发布上线后,线上该问题没有再复现,问题暂时告一段落。

总结

  • 对于线上故障首先要保护现场。
  • 对于大对象操作要特别小心。
  • 常用工具要熟练掌握,否则就会浪费很多不必要的时间。使用mat去分析问题的时候,更应该关心对象比例,而不是大小。如果熟悉工具更多一点,可以省去后面堆外空间的排查工作。
  • 有告警要及时看,幸好本次只挂掉了一台机子,对线上没有太大影响。

后续问题

后续调研发现,当请求量小的时候,内存上去后没有OOM,但是内存一直没有往下降,怀疑出现了内存泄漏。使用mat的Histogram搜索TrueTypeFont


再查看他的根回收节点。


过滤掉弱引用等对回收没有影响的引用。


发现主要有这么几个类,我们看最多的类Disposer。上网查了下Disposer的作用。


在mat中,我已经过滤掉了弱引用,剩下的发现有个FontStrikeDisposer对TrueTypeFont为强引用。但是这些数量比较少,只有8个,应该起不到作用才对。到此,头绪就断了,回头撸撸TrueTypeFont的源码看能不能有什么发现。