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

于是习惯性的打开了百度。再看了若干经验之谈后,准备下手了。步骤如下:

  1. 先找到服务器下oom错误快照
  2. 使用工具分析错误原因
  3. 结合代码定位到异常发生的地方

查找错误快照

不明白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的定位,还有别的很强大的工具,需要去多接触
发布了59 篇原创文章 · 获赞 33 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/weixin_41010294/article/details/104009722
今日推荐