内存溢出梅开二度,没想到你才是真凶!!!

一.问题

书接上文,没想到吧居然还有下集😬😬

昨天这个Excel导出的功能又内存溢出了,我都要哭了
我这次决定规规矩矩,好好地找一下到底是什么原因造成了内存溢出

二.分析问题

  1. 百度研究了一下,按照步骤操作了一波
  2. 首先需要生成一个dump文件用于内存分析
  3. 在项目运行配置中修改配置

修改项目运行时的配置文件
在这里插入图片描述

配置说明
-Xms200m设置内存最小值
-Xmx200m设置内存最大值
-XX:+HeapDumpOnOutOfMemoryError出现内存溢出的时候生成dump
-XX:HeapDumpPath=/Users/xfn/Download/dump文件生成的位置
  1. 请求内存溢出的接口,系统内存溢出(如果没有溢出就尝试把上面的内值调小一点),可以看到已经生成了一个hprof结尾的文件

在这里插入图片描述

三.下载分析工具

  1. mat是一个很好用的内存分析工具,这里附上下载地址

这里需要注意一下mac电脑直接打开可能会报错,需要右键显示包内容然后运行MemoryAnalyzer

在这里插入图片描述

在这里插入图片描述

  1. 导入dump文件

在这里插入图片描述

  1. 这么多的功能按钮我也不是特别清楚是什么作用,但是这个Leak Suspects可以精确到内存溢出的代码
    在这里插入图片描述

  2. 这里列出了几个可能造成内存溢出的地方,点击See stacktrace可以看到详细的报错行

在这里插入图片描述

  1. 分析这算错误信息,可以看出来,POI导出占用了一部分的内存,但是这部分内存是必须占用的,已经修改为SXSSF,已经不太好继续优化了,所以接着往下看

在这里插入图片描述

  1. 分析另一个问题,这里好像是因为产生了过多的VcJv这个对象造成的内存消耗,这么一看好像就是这里的可能性大一点了

在这里插入图片描述

  1. 按照异常的提示一步步知道出现异常的代码行,但是这个只是一个查询操作,理论上不应该出现内存溢出的问题,又没有生成那么多的对象

在这里插入图片描述

  1. 到这为止我又没有什么头绪了,只能开始排查代码,我发现了一块代码在循环里面执行了对象的初始化操作,这种操作简直恐怖,你这内存咋能不溢出呢

在这里插入图片描述

  1. 正当我准备开始改这段代码的时候,测试和我说了个很重要的信息,说我改过之后比之前更容易出现内存溢出这个问题,之前2w条数据才会出现内存溢出,现在1w不到就出现了(什么!你是不是觉得我的代码写的垃圾!!!不对。。。。我好想知道是什么原因了

  2. 这是一段请求超时降级的代码,是我加上去的,就是说当这个请求超过6s还没有请求结束,就终止这个请求提前给前端响应,然后开始一个新的线程继续执行这个任务,并将下载的文件发送到文件服务器

    public String degradeExportAssSubAll(String userName, String defaultsobid, String begin, String end, String asscategory, String export, HttpServletResponse response) {
        FutureTask<String> future = new FutureTask<>(() -> {
            // 直接下载到本地
            exportAssSubAll(userName, defaultsobid, begin, end, asscategory, export, response, false);
            return "0";
        });
        // 执行这个任务
        XFNThreadPool.excelPool.execute(future);
        try {
            // 监听任务执行的时间
            future.get(timeOut, TimeUnit.MILLISECONDS);
        } catch (Exception e) {
            // 如果超时,中断这个任务
            future.cancel(true);
            // 执行降级策略,下载到协同服务
            XFNThreadPool.exportFallBackPool.execute(() -> exportAssSubAll(userName, defaultsobid, begin, end, asscategory, export, response, true));
            // 抛出异常给前端
            return "16814";
        }
        return "0";
    }

  1. 理论上FutureTask提供的这个cancel(true)方法应该是可以终止这个任务的,那如果这行代码没有终止任务,是不是就会出现测试说的这种现象,于是我翻阅了这段源码,发现他是用interrupt()来终止线程的(原来就是你造成的原因啊

interrupt:仅仅是在当前线程中打一个停止标记,并不是真正的停止线程,也就是说,线程并不会立即终止,而是通知目标线程,有人希望你终止

在这里插入图片描述

  1. 我觉得可能就是这段造成的内存溢出,任务刚开始执行的时候,超时,但是这个任务并没有真的被中断,内存并没有被释放掉,然后我又开始了一个线程重新执行这个任务,从而导致的内存溢出

  2. 修改代码,将数据原先的数据查询和导出拆分开为查询任务和导出任务,只监控查询任务,当查询任务超时时,不中断这个任务,提前给前端响应,然后等查询任务执行结束,获取返回值,再调用导出任务,这么写避免了任务执行2次而产生的内存溢出问题,也提高了效率

public String degradeExportAssSubAll(String userName, String defaultsobid, String begin, String end, String asscategory, String export, HttpServletResponse response) {
        // 创建一个异步监听任务
        FutureTask<JSONArray> future = new FutureTask<>(() -> {
            // 执行数据查询任务
            return getAssSubAll(defaultsobid, begin, end, asscategory);
        });
        // 执行这个任务
        XFNThreadPool.excelPool.execute(future);
        try {
            // 监听任务执行的时间
            future.get(timeOut, TimeUnit.MILLISECONDS);
            // 任务没有超时,将Excel下载到本地
            exportAssSubAll(begin, end, userName, export, defaultsobid, false, future.get(), response);
        } catch (Exception e) {
            log.info("任务执行超过" + timeOut + "ms.....抛出异常" + e.toString() +"......开始执行降级任务....");
            // 任务超时,将Excel下载到协同服务
            try {
                // 创建一个新的线程,执行导出方法
                XFNThreadPool.excelPool.execute(() -> {
                    try {
                        // 等待异步任务执行结束,获取结果数据,再执行导出任务
                        exportAssSubAll(begin, end, userName, export, defaultsobid, true, future.get() , response);
                    } catch (Exception ex) {
                        ex.printStackTrace();
                    }
                });
                // 提前给前端响应
                return "16841";
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
        return "0";
    }
  1. 上测试环境调试,发现在3w+数据的情况下,内存也不会溢出

四.总结

你以为我想说的是mat的使用?其实我想说的是FutureTask😬😬
讲道理遇到问题还是一步步理性分析的好

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值