1

一次诡异的内存泄露排查过程,背后原因令人深思

 2 years ago
source link: https://blog.csdn.net/AI_Green/article/details/122072705
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

一次诡异的内存泄露排查过程,背后原因令人深思

同时被 3 个专栏收录
614 篇文章 1 订阅
618 篇文章 3 订阅
615 篇文章 15 订阅

在这里插入图片描述

前几天,群里一个学员发消息,说在压测时发现某应用程序jvm中FullGC特别多,不知道正常不正常,并把gc的截图发了出来。点开一看是这个样子

图片

这是 jstat监控 的截图,FGC那列代表数字代表从应用启动开始到目前为止FullGC发生的次数,数据是每秒打印一行,所以从图里可以看出,FullGC次数从2735到2736,只用了10秒,也就是大概10秒就会发生一次FullGC,这种频率对于一个Java程序来说太过于频繁了。每次FullGC都会造成应用程序的暂停,从而引起响应时间边长,程序性能也严重下降。从图里也能印证这一点,每次FullGC花费的时间在400ms多。

图里也有一个奇怪的现象,一般发生FullGC,都是因为jvm中老年代空间不足引起的,图中第4列就是老年代的数据,图里可以看到,老年代只占用了3%左右,远远达不到上限100%。

学员表示在这种情况下,用10并发去压测接口,tps大概900多,看起来性能还可以,但是 FullGC这么频繁肯定是有问题,还是需要排查下的。

也没考虑太多,既然FullGC这么频繁,那就看看jvm中对象的分配情况吧,于是让同学用 jmap 去打印下堆内存中的对象信息,结果如下:

图片

前4行分别是jdk里自带的数据对象,char[]、byte[]、int[]和String对象,这种一般都是正常的,看不出什么问题,可以忽略。

第5行和6行根据类名可以看出是fastjson的对象,fastjson主要用于json转换,是Java中常用的一种json序列化组件。除此之外,其他的对象都是jdk自带的,没有跟业务相关的对象。因此可以猜测,大概率是因为fastjson组件引起的。

又看了下学员提供的其他截图,发现应用程序jvm参数那里配的不太合理

  • 没有配新生代的大小,这样jvm会使用默认值
  • 永久代参数配置有误,从jdk8起,永久代的参数已经从PermSize改成了MataSpace

图片

于是先让学员把参数都改下,毕竟排查问题的基础是参数配置合理,也没准就是因为参数的问题导致的呢。

学员改完后又重新压测了下,这次FullGC的频率下降了很多,大概30秒FullGC一次。JVM的回收也很规律。

图片

貌似看着参数修改是起了一定的效果,但是学员表示现在tps降低了,jmeter显示tps大概在100-200之间,比刚才修改前的900要低很多

图片

这就尴尬了,越调tps越低了,于是又把目光转到刚才的fastjson上。学员正好有服务端代码查看权限,于是就让学员看看代码中哪些地方用到了fastjson。

学员说这个接口的逻辑非常简单,就是从数据库里查询用户信息,然后将数据转换为json字符串返回:

图片

在handle方法中,果然用到了fastjson

图片

这块代码写的比较简单,貌似看也没啥问题。为了印证确实是handle函数的问题,使用模块隔离法进行验证下。让学员跟开发反馈下,不要使用handle函数处理了,直接返回一个写死的json,然后验证下。

修改代码后,用jmeter又压了一次,接口的tps能到1400+,并且FullGC也很正常,这就说明是确实是handle函数代码有问题引起的。

将代码恢复原样,再次进行压测排查时,学员又发来另外一个jvm的监控截图,说metaspace空间的波动曲线和类加载的波动曲线比较一致,会不会是代码中有对象实例生成到metaspace中了,并且没有释放掉

图片

当看到这张图之后,恍然大悟。上升的曲线说明metaspace中在不断的加载类,且加载到接近上限时,会触发一次回收。释放了部分类,会造成一个波谷。学员也观察到metaspace波谷出现的频率和FullGC频率一致,那说明是metaspace回收引起的FullGC。

这种情况确实也比较少见。在这里跟大家解释下,metaspace是jvm中的一个内存空间,里面主要存放了类的基本信息、静态变量、常量等。一般来说,在java程序运行过程中,每个类第一次创建的对象时,会把类信息加载到metaspace中,且只加载一次,后续无论多少并发和请求,类不会重复加载的。因此metaspace的空间使用是非常稳定的,基本上不会随着并发的变化而变化。在多数的压测过程中,可以看到mataspace的内存占用是一条直线。只要在应用启动的时候,给metaspace一个比较大的初始空间,是不会造成FullGC的。

当然了,有一种特殊情况除外。如果代码中存在动态加载类的情况,那每个线程在执行代码时,都会重新加载类到metaspace中,通常在使用反射的场景中用的比较多。

目光又回到代码中的handle方法,在此方法中,果真是有一行动态加载类的代码

图片

在创建了config对象后,会put一个Long.class,这个时候就会把Long这个类加载到metaspace中,而且handle方法是每次请求都会调用。所以metaspace空间才增长这么快。当然了,单纯每次创建对象,并不会造成内存溢出,这就是为什么老年代的使用量并不是很高的原因。问题还是出在了每次都加载了Long.class

在代码中,config对象是一个配置对象,其实并不需要每次调接口都创建一个对象,然后又加载一次配置。可以做成全局的静态变量,然后加载一次配置即可。

代码修改如下,定义为静态变量,然后在静态代码块中进行初始化

图片

使用jmeter重新进行验证,在10并发下,tps现在能跑到1400左右,并且没有出现FullGC了。此问题得到了解决,而且因为handle是项目里的一个公共方法,此问题解决会将项目里的所有接口性能提升一个台阶。

事后回想起来,其实这个问题本应该早就被发现的,因为在最早的gc截图里,已经能看出来是metaspace造成了FullGC,只不过当时没太关注metaspace,再加上文本式的打印不如曲线图那么直接

图片

这也给所有使用fastjson的同学提个醒,在使用序列化配置功能的时候,切记配置对象要定义成全局静态的,否则就会造成元空间内存溢出,从而触发FullGC,造成应用程序性能的下降。

技术行业要不断地学习,学习肯定不要孤军奋战,最好是能抱团取暖,相互成就一起成长,群众效应的效果是非常强大的,大家一起学习,一起打卡,会更有学习动力,也更能坚持下去。你可以加入我们的测试技术交流扣扣群:914172719(里面有各种软件测试资源和技术讨论)

送给大家一句话,共勉:当我们能力不足的时候,首先要做的是内修!当我们能力足够强大的时候,就可以外寻了!

最后也为大家准备了一份配套的学习资源,你能在 公众号:【伤心的辣条】免费获取一份216页软件测试工程师面试宝典文档资料。以及相对应的视频学习教程免费分享!,其中资料包括了有基础知识、Linux必备、Shell、互联网程序原理、Mysql数据库、抓包工具专题、接口测试工具、测试进阶-Python编程、Web自动化测试、APP自动化测试、接口自动化测试、测试高级持续集成、测试架构开发测试框架、性能测试、安全测试等。


转行面试,跳槽面试,软件测试人员都必须知道的这几种面试技巧!

面试经:一线城市搬砖!又面软件测试岗,5000就知足了…

面试官:工作三年,还来面初级测试?恐怕你的软件测试工程师的头衔要加双引号…

什么样的人适合从事软件测试工作?

那个准点下班的人,比我先升职了…

测试岗反复跳槽,跳着跳着就跳没了…

包装成1年工作经验的测试工程师,我给他的面试前的建议如下

“入职一年,那个被高薪挖来的自动化软件测试被劝退了。”

4个月自学软件测试面进阿里!如何从功能测试转成自动化…我经历了什么

6000元报了培训班,3个月后我成功“骗”进了腾讯大厂,月薪15000

在这里插入图片描述


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK