内存溢出线上问题定位案例

问题定位

阶段一:系统每月月结前后,出现内存溢出报警,前期怀疑是核算线程池使用不当造成,于是在核算线程打上标记(标记线程名称),经过几次定位(jmap和jstatck命令)发现核算过程正常 未找到问题根源,但可确定线上内存溢出问题,不是核算造成的

阶段二:8月支持7月月份月结时候,(有一天上午)线上两个节点,有一台节点直接不可用(事后确认通过nginx负载,另一台节点也不可用),通过90节点后台日志定位,发现“导出”导致堆内存溢出(java.lang.OutOfMemoryError: Java heap space),保证月结正常进行,当时采用快速重启节点;

8月20 10:30左右,又收到运维报警短信,去线上90节点,查询日志 在这个时间点前后 用户 做了 “导出”操作

报警过后通过jmap命名未定位到有用的信息

阶段三:线上验证(慎用,可能会造成节点不可用),连续执行三次主管业绩明细导出操作,两个请求负载到91节点 一个请求负载到90节点 ;重点关注91节点

打开日志 tail -f /opt/web/bp_pms_new_house/logs/catalina.out

查看系统进程ID ps -ef | grep pms_newhouse 7869

执行导出大约2分钟,收到运维发送堆内存(超80%)报警短信,主要过程有以下几步:

1.通过top命名 shift+P 或 U 分别根据CPU使用和内存使用进程排序

CPU使用第一果断是系统A进程ID

2.通过进程ID定位出消耗资源线程ID top -Hp 7869 同样排序与1步骤差不多

发现大量 79XX和78XX的线程 消耗资源

重点:定位出线程是垃圾回收线程 or 业务线程

3.通过jstack命名 导出线程堆栈信息 jstack 7869

注意:top -Hp 显示线程pid是十进制 要转成16进制 查找对应nid

综上,发现大量pid=7XXX 对应nid=0x1e开头 是垃圾回收线程, 故判断内存到达一定阈值 启动大量垃圾回收线程 回收对象释放堆内存空间

4.通过jmap命名查看堆中对象 jmap -histo:live 7869 | head -20 定位前20个

备注:若收到公司短信 已经说明内存问题了 可直接用4步骤定位

根据占用内存对象,定位代码 SupervisorScoreDetailVO>Xobj$ ..... poi组件用到的对象

最终定位到poi(easypoi)组件问题:

XSSFWorkbook在创建Excel、或者说写Excel的时候内存开销是很大的

线上环境简单描述:jdk8 、cms、堆内存大小2G

问题解决

本地环境搭建

工具:IDEA + JDK8 + jvisualvm(jdk自带)

参数配置:-Xms1024M -Xmx1024M -XX:+PrintGCDetails -XX:+UseConcMarkSweepGC

数据准备:t_test 准备6万多条数据 用于导出验证

本地jetty插件启动 需要配置上参数

本地验证

导出操作,监控堆内存情况

执行一次导出操作 6万多条数据 如图3:57到3:58 直接快达到jvm内存最大值 ,系统前端界面处理假死状态

后台GC日志 不停回收内存 但每次只能回收一点点 (导出未完成 poi创建大量内部对象,可通过定位问题那几步验证 这里不再重复) 最终堆内存溢出

具体代码定位和修改

通过导出功能 一步一步跟踪 定位到 ExportThread

根据easypoi源码

虽然设置ExcelType.XSSF 但是当不满足 100000条时候 任然使用XSSFWorkbook(问题出在这个类,创建大量对象,特别占用内存 有兴趣可去研究下)

定位到问题后 后台代码 新建ExcelExportUtilAdapter 替换 ExcelExportUtil 具体如下

去掉 100000判断逻辑 直接用SXSSFWorkbook

最终修改业务调用代码

本地验证

连续导出5次 监控堆内存情况

至此,内存溢出问题分析、定位和解决已完成

posted @ 2020-10-12 19:58  mindy3250  阅读(675)  评论(0编辑  收藏  举报