性能文章>实战:一次疑似内存泄漏的问题排查>

实战:一次疑似内存泄漏的问题排查原创

7391312

问题背景

最近服务器到期等因素,进行了迁移。租了其它的外国厂商,但是由于资费问题,购买了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

问题解决过程

诊断排查出现内存泄漏对象

image.png

这个文件占用了差不多535M,说明里面的对象占用空间很大。这里我使用了MAT工具来排查。

image.png

1)Histogram (可以查看每个类的实例(即对象)的数量和大小)

image.png

通过Histogram图,我们初步看出,实际上跟我们自己的代码好像是没有关系,因为这里没找到我们对应的包名和类名。不过可以看到一些疑惑就是Tomcat和mysql相关的类占用了很大内存。

2)Dominator Tree(列出Heap Dump中处于活跃状态中的最大的几个对象,默认按 retained size进行排序)

image.png

从这里可以更加明确是哪些对象占用了大部分资源了,似乎也是跟Tomcat和mysql有关。

3)Top Consumer(按类、类加载器和包分别进行查询,并以饼图的方式列出最大的几个对象)

image.png

通过上面这个饼图,我们更加明确的是哪些对象占用了大部分资源。其它一些视图这里就不展开了。

小结:上面MAT的各种视图表明,并不是说占用资源大的对象是就是内存泄漏的罪魁祸首,它这里只是做了个统计,方便你观察和发现问题,只是提示你可能这些对象存在泄漏的可能性。

诊断排查占用大资源内因

由上面的图中,我从TaskThread这个类入手:

image.png

发现主要占用的资源在StatefulPersistenceContext这个类,很明显这个是Hibernate持久化相关的。

image.png

查看下类的注释,大致意思是:

PersistenceContext表示Hibernate正在跟踪的持久化“内容”的状态。这包括持久实体、集合以及生成的代理。

SessionImpl和PersistentContext之间应该是一对一的对应关系。SessionImpl使用PersistentContext来跟踪其上下文的当前状态。事件监听器使用PersistentContext来执行处理。

因为Hibernate的一级缓存就是在Session层面上,所以StatefulPersistenceContext跟一级缓存有关系,网上有些资料也有说StatefulPersistenceContext存在问题可能造成内存泄漏等原因。

可以在使用完Session时进行clear清除。这样就能防止占用过大资源,但是,使用一级缓存是能够大大的增强性能,如果这么做的话,似乎有违背Hibernate的设计初衷,因此,这里我们不能采取这种做法。

继续往下挖,看这里面存放的是具体什么对象吧!

image.png

从这里可以看出,持久化的主要对象实体是ShareArticle,并且有1706个实体之多!~,这里只是TaskThread这个线程所持有这么多的实体,要知道下面还有几个大对象TaskThread,里面也是有包含这些的,所以不仅仅1千个多实体。

找出问题根源(“真凶”)

image.png

看到这个实体的每个字段,才知道问题出现在哪!这张表有一个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。

请先登录,查看3条精彩评论吧
快去登录吧,你将获得
  • 浏览更多精彩评论
  • 和开发者讨论交流,共同进步

为你推荐

关于内存溢出,咱再聊点有意思的?
概述 上篇文章讲了JVM在GC上的一个设计缺陷,揪出一个导致GC慢慢变长的JVM设计缺陷,可能有不少人还是没怎么看明白的,今天准备讲的大家应该都很容易看明白 本文其实很犹豫写不写,因为感觉没有
字符串字面量长度是有限制的
前言 偶然在一次单元测试中写了一个非常长的字符串字面量。 正文 在一次单元测试中,我写了一个很长的字符串字面量,大概10万个字符左右,编译时,编译器给出了异常告警 `java: constant
多次字符串相加一定要用StringBuilder而不用-吗?
今天在写一个读取Java class File并进行分析的Demo时,偶然发现了下面这个场景(基于oracle jdk 1.8.0_144): ``` package test; public c
如何通过反射获得方法的真实参数名(以及扩展研究)
前段时间,在做一个小的工程时,遇到了需要通过反射获得方法真实参数名的场景,在这里我遇到了一些小小的问题,后来在部门老大的指导下,我解决了这个问题。通过解决这个问题,附带着我了解到了很多新的知识,我觉得
谨防JDK8重复类定义造成的内存泄漏
概述 如今JDK8成了主流,大家都紧锣密鼓地进行着升级,享受着JDK8带来的各种便利,然而有时候升级并没有那么顺利?比如说今天要说的这个问题。我们都知道JDK8在内存模型上最大的改变是,放弃了Perm
高吞吐、低延迟 Java 应用的 GC 优化实践
本篇原文作者是 LinkedIn 的 Swapnil Ghike,这篇文章讲述了 LinkedIn 的 Feed 产品的 GC 优化过程,虽然文章写作于 April 8, 2014,但其中的很多内容和
「每日五分钟,玩转 JVM」:久识你名,初居我心
聊聊 JVMJVM,一个熟悉又陌生的名词,从认识Java的第一天起,我们就会听到这个名字,在参加工作的前一两年,面试的时候还会经常被问到JDK,JRE,JVM这三者的区别。JVM可以说和我们是老朋友了
JVM垃圾回收与一次线上内存泄露问题分析和解决过程
本文转载自:花椒技术微信公众号 前言内存泄漏(Memory Leak)是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。Ja