实战:一次疑似内存泄漏的问题排查原创
问题背景
最近服务器到期等因素,进行了迁移。租了其它的外国厂商,但是由于资费问题,购买了1.5G 内存的服务器(现)。因为原本用惯了4G内存的服务器(原),现在压缩成这样,似乎不太能支持我的使用,囧!
现在就来说下blog服务分配的内存情况:
原:4G 内存,分配给blog,1.5G。
现:1.5G内存,分配给blog,500M。
由于此次调整,原本以为资源需求压力会更大。实际上应该不会,仔细想想,blog也没多少内容的。那么根据此次调整之后,已经出现了2次OutOfMemoryError了。
由于JVM参数配置有加上-XX:+HeapDumpOnOutOfMemoryError
,所以出现OutOfMemoryError
时会将堆栈输出到指定的文件,这样可以方便以后排查问题。
OS:CentOS7
Web容器:Tomcat 8
ORM:Hibernate3
数据库:MySQL5
问题解决过程
诊断排查出现内存泄漏对象
这个文件占用了差不多535M,说明里面的对象占用空间很大。这里我使用了MAT工具来排查。
1)Histogram (可以查看每个类的实例(即对象)的数量和大小)
通过Histogram图,我们初步看出,实际上跟我们自己的代码好像是没有关系,因为这里没找到我们对应的包名和类名。不过可以看到一些疑惑就是Tomcat和mysql相关的类占用了很大内存。
2)Dominator Tree(列出Heap Dump中处于活跃状态中的最大的几个对象,默认按 retained size进行排序)
从这里可以更加明确是哪些对象占用了大部分资源了,似乎也是跟Tomcat和mysql有关。
3)Top Consumer(按类、类加载器和包分别进行查询,并以饼图的方式列出最大的几个对象)
通过上面这个饼图,我们更加明确的是哪些对象占用了大部分资源。其它一些视图这里就不展开了。
小结:上面MAT的各种视图表明,并不是说占用资源大的对象是就是内存泄漏的罪魁祸首,它这里只是做了个统计,方便你观察和发现问题,只是提示你可能这些对象存在泄漏的可能性。
诊断排查占用大资源内因
由上面的图中,我从TaskThread这个类入手:
发现主要占用的资源在StatefulPersistenceContext这个类,很明显这个是Hibernate持久化相关的。
查看下类的注释,大致意思是:
PersistenceContext表示Hibernate正在跟踪的持久化“内容”的状态。这包括持久实体、集合以及生成的代理。
SessionImpl和PersistentContext之间应该是一对一的对应关系。SessionImpl使用PersistentContext来跟踪其上下文的当前状态。事件监听器使用PersistentContext来执行处理。
因为Hibernate的一级缓存就是在Session层面上,所以StatefulPersistenceContext跟一级缓存有关系,网上有些资料也有说StatefulPersistenceContext存在问题可能造成内存泄漏等原因。
可以在使用完Session时进行clear清除。这样就能防止占用过大资源,但是,使用一级缓存是能够大大的增强性能,如果这么做的话,似乎有违背Hibernate的设计初衷,因此,这里我们不能采取这种做法。
继续往下挖,看这里面存放的是具体什么对象吧!
从这里可以看出,持久化的主要对象实体是ShareArticle,并且有1706个实体之多!~,这里只是TaskThread这个线程所持有这么多的实体,要知道下面还有几个大对象TaskThread,里面也是有包含这些的,所以不仅仅1千个多实体。
找出问题根源(“真凶”)
看到这个实体的每个字段,才知道问题出现在哪!这张表有一个content字段,类型是mediumtext,存放文章内容。所以一旦加载到内存里,自然的需要占用大部分资源了。
找到问题的点在哪了,接下来就得看怎么优化,毕竟在资源急缺的我,需要优化下当前的服务了。
解决问题,优化!优化!
优化之一:查询不返回content字段
特别是哪些查询列表相关的。这能大大的减少占用内存。因为有些查询List结果实际上是没有使用到content字段,再次查询出来也是一种浪费。
说下这个过程吧,由于使用的是Hibernate,虽说有其优点,但是使用起来极其不灵活!在公司用惯了Mybatis,才知道Mybatis的好,哈哈!
优化之二:只查询需要的字段
在查询当前文章的上下文时,基本上也是不用用到content字段,这里只需要返回id和title,即可。其实跟上面2.4.1的类似。
优化之三:延迟加载指定字段
我们知道,Hibernate在一对多、多对多等关系中,是支持延迟加载的。查资料发现Hibernate3也能支持指定字段进行延迟加载,在需要的时候再次去查询数据库指定的字段再返回。
所以就动手干!但是也遇到使用时出现不生效,资料表明需要再次使用字节码进行增强才能正常使用。
小结:经过上面的优化,服务再也不会出现OutOfMemeryError了,而且资源占用也大幅度降低。
总结(经验与优化)
1)针对占用资源多的,是否能够不存在数据库,比如生成静态HTML文件,访问的时候直接包含在页面直接返回,这样能快速返回,占用内存少,提升性能。
2)针对大字段(占用资源大),没用到时不返回。
3)只返回需要的字段,在SQL优化的上必有,这样也能提升mysql的吞吐量,也不会浪费资源。
4)如果追求灵活性,ORM建议使用mybatis,毕竟互联网公司基本用它。Hibernate更适合在企业系统里面使用。
5)如果已经使用了Hibernate了,可以增加字段延迟加载机制,进而在需要的时候再去查询。
本文来自:搬运工来架构 公众号,作者:cocodroid。