某天凌晨,手机忽然告警,线上某台机子内存使用率超过90%,当时觉得是有定时任务在跑,再加上夜已深了,没有去排查具体缘由。次日早上有发布,内存降下来了,白天就没有去调查这个问题。等到傍晚高峰期的时候,又接到内存调用超过90%的告警,而且持续一段时间后,线上的某台机子挂了。
当时一台机子挂掉后,立刻重启了挂掉的机子,而且把另外一台机子的内存信息dump下来。
java
jmap -dump:format=b,file=文件名 [pid]
服务器
在dump过程当中遇到小插曲,没法dump下来。jvm
这种状况是由于非当前线程用户致使,在命令前面加上sudo -u 用户
便可
工具
同时保存了线程信息 测试
sudo -u jetty jstack pid > /tmp/jstack2018-04-18.txt
字体
查看服务器系统日志
cat /var/log/messages
spa
上图看到 Killed process 7364, UID 502, (java) total-vm:10511252kB, anon-rss:7489308kB
java内存使用了7G多。ps:上一行的java进程的2627813 和 1872416都是页数,每页4K。.net
能够发现容器内存使用由于超过系统内存被kill掉了。线程
使用mat分析具体内存泄漏问题。
因为本人对mat工具使用的还不熟练,看到分布图后,total一共才103.6MB,觉得堆内内存没有问题,便往堆外内存溢出方向去考虑。陆陆续续的检查了代码中的ThreadLocal的使用,对象也有及时清理,检查了线上线程的数量,都没有发现明显问题。3d
转载一篇线程过多致使的堆外内存溢出文章:线程数过多致使堆外内存溢出
继续使用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改成单例模式,内存中只存在一份。
在公司测试环境再次试了下,内存使用率没有变化。发布上线后,线上该问题没有再复现,问题暂时告一段落。
后续调研发现,当请求量小的时候,内存上去后没有OOM,可是内存一直没有往降低,怀疑出现了内存泄漏。使用mat的Histogram搜索TrueTypeFont
再查看他的根回收节点。
过滤掉弱引用等对回收没有影响的引用。
发现主要有这么几个类,咱们看最多的类Disposer。上网查了下Disposer的做用。
在mat中,我已通过滤掉了弱引用,剩下的发现有个FontStrikeDisposer对TrueTypeFont为强引用。可是这些数量比较少,只有8个,应该起不到做用才对。到此,头绪就断了,回头撸撸TrueTypeFont的源码看能不能有什么发现。