线上java内存泄露处理实录1

1,738

线上现象(各种监控数据)

1.公司项目在监控平台上开始报警(jvm堆内存占用报警,FullGC次数超频率报警)

jvm内存泄露 bob
2.观察具体的监控图标(预发机器) 线程数平稳(260左右)
jvm内存泄露 bob
jvm内存泄露 bob
jvm内存泄露 bob
jvm内存泄露 bob
3. 方法监控可以看到在fullGC比较频繁时,业务方法几乎无响应
jvm内存泄露 bob

线上配置(jvm配置,运行时内存分布)

  1. 项目版本:jdk8 ,spring 5, 默认垃圾处理器 Parallel GC with 43 thread(s) -Xms800m -Xmx800m -XX:MaxPermSize=256m
    jvm内存泄露 bob
    2.运行时jstat
    jvm内存泄露 bob
    3.运行时jmap histo
    jvm内存泄露 bob
    4.heap
    jvm内存泄露 bob
    5.dump文件1G左右,不发了,稍后看一下MAT分析的图表吧

逻辑分析(定位问题大致方向)

1.通过监控和运行时数据分析,堆内存(年轻代和老年代)、非堆(方法区)、均打满配置内存 2.即使FullGC,堆内存和非堆也只能回收少许内存,并且整体水位倾斜向上,直到内存溢出

通过逻辑分析,内存溢出问题来自于存在泄漏,接下来分析dump文件

内存分析(定位问题确切泄漏源)

采用MAT工具载入dump文件进行leak分析

jvm内存泄露 bob
jvm内存泄露 bob
通过分析可以看出红色的业务方法保留引用太多, 找到泄漏源了,接下来就分析业务代码具体的问题

代码分析(定位导致泄露代码片段)

jvm内存泄露 bob
jvm内存泄露 bob
通过分析,我们最终定位了这段代码,我们改造过程中引入了一个开源的属性运行时拷贝的包 但是我们每次转换的时候都会先register一遍转换代理类,而此类底层为每次register注册一个新生的代理类被加载到非堆 但是又被业务代码中的MAPPER_FACTORY引用,导致每次生成的实例充斥着年轻代又到老年代

最终的现象就是老年代、年轻代、非堆内存同时爆满,而又GC不掉,内存泄露直到溢出

代码处理

找到了具体的代码问题,我们将同一个类转换的register在系统启动时注入一次就行,不用每次调用注册,这样的话就不会频繁创建和加载,就可以解决上述问题

本地验证

本地验证错误使用代码,可以复现问题

jvm内存泄露 bob
jvm内存泄露 bob
jvm内存泄露 bob
加入 参数-verbose -verbose:gc 也 可以看到新增的代理类在循环中疯狂生成与加载, 修复后本地监控数据平稳运行,具体图标参考预发验证图表

预发验证

jvm内存泄露 bob
jvm内存泄露 bob
jvm内存泄露 bob
jvm内存泄露 bob
预发验证后续我们还将采用压测排除性能与其他内存问题,此次排除结束。

后记

此次问题还是属于比较常见的内存溢出分析,整体按着常用流程没有太多的难点,只有在分析register的时候一时定位不到是开源包的register方法调用的原因(虽然事后感觉很简单,当时也是耗费了30分钟左右才发现)。

troube shooting 三要素: 锻炼自己的逻辑思维、锻炼自己的技术能力、多看多查