OOM别慌,手把手教你定位
本渣渣今早正在写BUG呢,TL丢过来一个问题,说是平台有异常让我康康。一顿操作找到错误日志后,傻眼了OutOfMemoryError,这玩意我也就会写写,也没定位过啊。
org.springframework.transaction.CannotCreateTransactionException: Could not open JPA EntityManager for transaction; nested exception is java.lang.OutOfMemoryError: Java heap space
at org.springframework.orm.jpa.JpaTransactionManager.doBegin(JpaTransactionManager.java:446)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(AbstractPlatformTransactionManager.java:378)
at org.springframework.transaction.interceptor.TransactionAspectSupport.createTransactionIfNecessary(TransactionAspectSupport.java:475)
Caused by: java.lang.OutOfMemoryError: Java heap space
于是习惯性的打开了百度。再看了若干经验之谈后,准备下手了。步骤如下:
- 先找到服务器下oom错误快照
- 使用工具分析错误原因
- 结合代码定位到异常发生的地方
查找错误快照
不明白heap dump的看这个:https://www.jianshu.com/p/91ee6476848c
我的是在这里
当然可以通过find命令来查找,一般都在tomcat的目录下
以hprof结尾的就是目标文件了
使用工具分析
MemoryAnalyzer.exe
下载链接:https://www.eclipse.org/mat/downloads.php(建议科学上网,70M左右)
打开之后,把上一步找到的快照文件导进去。注意可能有坑(一般线上环境的快照文件动辄上G,所以建议把工具的最大内存调大,如下)
MemoryAnalyzer.ini文件,我的文件有4.1G所以调的比较大。
-startup
plugins/org.eclipse.equinox.launcher_1.5.0.v20180512-1130.jar
--launcher.library
plugins/org.eclipse.equinox.launcher.win32.win32.x86_64_1.1.700.v20180518-1200
-vmargs
-Xmx5096m
成功导入之后会到欢迎界面,忽略就行。点击分析产生的Leak Suspects,如图:
其中Histogram比较常用:
占用内存排行,对于定位也很有帮助。饼图下方有存在的错误报错,自动生成的,主要是把占用内存高的展示出来了,意思就是你的问题在这里。
先看堆栈信息,找到报错的地方,这里会有具体的报错方法的信息(我没截全):
再看是什么对象导致的,点details
可以看到一共有接近1千万的对象,后面的内存占用也有,那么到这一步,可以去代码里面找了。
查找代码
@Override
public List<ResourceResp> queryResources(ResourcesVerifyReq resourcesVerifyReq) {
try {
ResponseData responseData;
long total = 1001;
HashMap<String, Object> queryParam = new HashMap<>(8);
int pageNo = resourcesVerifyReq.getPageNo();
queryParam.put("pageNo", pageNo);
int pageSize = resourcesVerifyReq.getPageSize();
queryParam.put("pageSize", pageSize);
if (pageSize > 0) {
responseData = RpcExchange.call(rpcParamDto, queryParam, Constants.HTTP_WEB_PORT);
} else {
pageSize = 1000;
queryParam.put("pageSize", pageSize);
while ((long) pageNo * (long) pageSize < total) {
responseData = RpcExchange.call(rpcParamDto, queryParam, Constants.HTTP_WEB_PORT);
List<ResourceResp> list = data.getList();
total = data.getTotal();
returnData.addAll(list);
}
}
return returnData;
} catch (Exception e) {
logger.error("资源查询出错",e);
}
}
// 相信你们看到这个while循环,心里也咯噔了一下,原来当查询后total大于1000时,这里就进入死循环了,原因竟然是,分页去查的时候,pageNo没进行递增的操作。
修改如下:
while ((long) pageNo * (long) pageSize < total) {
responseData = RpcExchange.call(rpcParamDto, queryParam, Constants.HTTP_WEB_PORT);
List<ResourceResp> list = data.getList();
total = data.getTotal();
returnData.addAll(list);
// 大于1000时,需要递增
queryParam.put("pageNo", ++pageNo);
}
总结如下:
// 1,写循环的时候,尽量考虑考虑次数是否明确,终止条件是否确定,小心死循环,小心死循环,小心死循环
// 2,定位OOM时,不用慌,工具其实替我们做了大量的工作,但你要会使用,我只是刚用了下皮毛
// 3,review的时候,多注意可能导致死循环的地方
// 4,OOM的定位,还有别的很强大的工具,需要去多接触
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~