堆空间溢出问题排查

1.案例背景

在一次系统测试过程中,测试人员反馈平台业务功能不可用,请求协助排查。
我首先查看运管平台-状态监控,检查服务运行情况,发现状态显示服务运行正常,初步判断可能是服务假死造成的,而能造成服务假死的多半是jvm出问题了。查看运管日志,发现异常日志出现Java heap space,即jvm堆内存溢出。本文将介绍一下我的分析思路与处理过程。

2.排查思路

首先,根据本人目前所掌握的jvm知识与经验,猜测引起内存溢出有以下几种情况:

  • 内存中加载的数据量过于庞大,如一次从数据库取出过多数据;
  • 集合类中有对对象的引用,使用完后未清空,使得JVM不能回收;
  • 使用的第三方软件中的BUG;
  • 启动参数内存值设定的过小;

2.1 异常信息收集

根据组件启动脚本可知,在JAVA_OPTIONS变量中已配置了HeapDumpOnOutOfMemoryError参数,当组件服务内存溢出时,将会存储dump文件,可以通过分析dump文件进行问题排查。

	-XX:+HeapDumpOnOutOfMemoryError

在这里插入图片描述

2.2 DUMP文件分析

获取Dump文件后,使用工具MAT(MemoryAnalyzer)加载dump文件进行内存分析。
1
通过MAT工具的Leak Suspects,该模块会自动分析内存溢出可疑点并给出一份可疑的分析报告。
在这里插入图片描述
在这里插入图片描述
从上面的截图中可以得出如下关键信息点:

  • 可以明确,堆的总大小约为1G。
  • 其中一个名为XNIO-2 task-113的线程内部持有一个List,该List中存放的对象占据了917.9MB内存,导致其他线程无法再申请资源,抛出堆内存溢出错误。
  • 服务基于springboot框架开发,web容器为undertow,undertow底层便是使用的XNIO进行异步IO处理,而xnio task线程抛出异常说明是由一次http请求触发的。
  • List变量中存放的是org.postgresql.jdbc.PgResultSet对象,PgResultSet表示pg数据库返回封装的结果集。从这一点可以初步推测,此次内存溢出的原因可能是在一次http请求查询数据时,从数据库一次查询出了太多数据导致了内存溢出。

2.3 线程栈分析

接下来,我通常的做法是直接去查看线程栈信息,通过线程栈可以定位到异常发生的线程入
口。

同样,通过MAT工具进入Thread Stack界面。查看上面提到的XNIO-2 task-113的线程栈信息,程序在执行ExportController.exportAlarmList()方法中的AlarmServiceImpl.queryAlarmListWithResult()时导致了内存溢出异常。

2.4 定位问题代码

找到ExportController.exportAlarmList()代码块(此处代码做了业务简化)。

/**
* 导出报警记录
* @param param
* @return
*/
public BaseResult exportAlarmList(@RequestBody @Valid
AlarmExportParam param) {
	// 封装参数
	AlarmQueryParam alarmQueryParam = newAlarmQueryParam();
	BeanUtils.copyProperties(param, alarmQueryParam);// 设置pageNo和pageSize
	alarmQueryParam.setPageNo(AlarmConstants.EXPORT_PAGE_NO);
	alarmQueryParam.setPageSize(AlarmConstants.EXPORT_PAGE_SIZE);
	BasePage<AlarmDTO> page = alarmService.queryAlarmListWithResult(alarmQueryParam);
	ExportResultVO exportResultVO = alarmDownloaderExport.getExportResult(AlarmDTO.class, age.getList(), param.getExportHasPicture());
	return BaseResult.success(exportResultVO);
}

根据线程栈信息可知,程序在调用alarmService.queryAlarmListWithResult()方法时发
生溢出,该方法是一个分页查询接口,那么进一步判断,分页每页查询数量可能过大。

BasePage<AlarmDTO> page = alarmService.queryAlarmListWithResult(alarmQueryParam);

查看AlarmConstants.EXPORT_PAGE_SIZE参数赋值。

public class AlarmConstants {
	public static final Integer EXPORT_PAGE_SIZE =Integer.MAX_VALUE;
}

根据此处代码可知,原来是在分页查询报警记录时,分页每页查询数量设置成了Integer最
大值,当查询的数据很大时,相当于查询了全部记录并返回。

2.5 解决方案

  1. 修改分页每页查询数量,设置为1000
  2. 调整组件服务堆内存最大空间大小为1.5G

3.总结

  1. 总体上来说,产生内存溢出是由于代码写的不好造成的,因此提高代码的质量是最根
    本的解决办法.
  2. 研发人员在开发与自测过程中,需要关注边界条件。
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值