1. 起源

同事找我说有个网站不知道是在漏扫还是压测的时候, 给弄爆了, OOM, 还是在访问首页index的时候, 然后给我这么一张截图

Untitled

可以看出OOM的原因是heap space, 所以决定dump一下快照出来分析一下(此时他已经重启了, 但是压测还在继续)。

2. 分析

使用arthas的heapdump命令把hprof文件dump出来, 把hprof文件导入mad(memory analyzer tool)

首先看这个图表Top Consumers:展现哪些类、哪些 class loader、哪些 package 占用最高比例的内存。

这个里面StatefulPersistenceContext和TaskThread占用内存空间特别大, StatefulPersistenceContext应该是hibernate的某种上下文, 里面应该托管了一些entity实体之类的, 便于跟数据库进行交互, 几个Thread应该就是用户发起的请求, 同时猜测StatefulPersistenceContext肯定是被Thread引用。

Untitled

然后查看dominator tree, Dominator tree 是根据对象引用及支配关系生成的树状图, 可以看出下图按Retained Heap (一个对象被 GC 回收后,可释放的内存大小,简单来说就是对象及其引用) 降序排序后, 有几个StatefulPersistenceContext和Thread占用空间特别大, 主要是聚集在一个StringWriter上面, 然后copy里面的内容出来看, 竟然是一篇新闻, 而且新闻里面有几十张base64格式的图片在里面, 所以这篇新闻占用的内存空间特别大。

Untitled

然后mat也自带了一些分析功能, 比如Leak Suspects (直击引用链条上占用内存较多的可疑对象,可解决一些基础问题,但复杂的问题往往帮助有限), 看图表中也基本上差不多是一致的, 给出的问题疑点也是一致的

Untitled

然后点进详情里一层层扒下去, 果然是从数据库中查出来的entity实体, 这随便找个entity, 里面有个字段43MB…….

所以问题到现在很清晰明朗了, 在访问首页的时候会加载新闻列表, 由于最近更新的几篇新闻都包含大量的图片, 而新闻富文本是直接存储在数据库中的, 里面的图片也是base64格式直接存储在数据库中的, 所以在频繁访问首页的时候, hibernate会持续从数据库中查出新闻的所有数据, 然后还会缓存在自己的上下文中之类的, 这样线程越来多, 最后把JVM撑爆。

解决: 只从数据库中查询新闻id和title, 把首页无用的查询去掉等。

Untitled

3. 总结

当然mat的功能也不止这些, 这篇文章就写了一些关于解决本次内存溢出的关键性操作和思路, 如果后面有机会的话再写一下mat的别的统计与筛查功能。