1.事件背景:
生产有个定时任务,经常跑不出数据,通过监控发现对应的那台机器内存一跑这个定时任务就会陡增。 由于应用部署在容器中,当内存跑满后会自动重启,所以导致定时任务无法执行完毕。上图:
内存飙升。
容器重启。
2.看代码进行分析
public ReturnT<String> batteryOperatingStateCheckJob(String param){
try {
Date date = DateUtil.offsetDay(new Date(),-1);
try {
if(StrUtil.isNotBlank(param)){
date = DateUtil.parse(param);
}
}catch (Exception e){
XxlJobLogger.log("参数["+param+"]格式错误:"+e.getMessage());
}
String yesterday = DateUtil.format(date,"yyyy-MM-dd");
Date beginDate = new Date();
log(yesterday+" 电池运营状态日盘点任务开始:当前每页记录数:"+pageSize);
//1.先清空表
batteryCheckService.truncateBatteryCheckDetail(yesterday);
//查询所有电池信息并插入Oracle
Query query = new Query();
//从mongo中统计数据总量
long total = secondaryMongoTemplate.count(query, BatteryStatusInfo.class);
//分页 一页1w数据
int pages =(int)Math.ceil((double)total/pageSize);
//循环插入数据库里面用了一个线程池5个线程
for(int i=0;i<pages;i++){
ExecutorService executor = ThreadUtil.newExecutor(5);;
query.skip(pageSize * i).limit(pageSize);
log("第"+(i+1)+"批数据:正在查询...");
//将mongo查出的数据按照1000一个分组成list
List<List<BatteryCheckInfo>> subList = getPageList(query,yesterday);
log("第"+(i+1)+"批数据:查询完成。");
//向线程池中添加任务
subList.forEach(sub-> executor.submit(
() -> {
log("sql执行中。。。");
batteryCheckService.batchInsertBatteryCheckDetail(sub, yesterday);
}
));
//等待线程池任务执行完毕后再进行下一次循环
executor.shutdown();
while (!executor.isTerminated()){
Thread.sleep(3000);
}
log("一次循环执行完毕:");
// System.gc();
}
}
}catch (Exception e){
e.printStackTrace();
return new ReturnT<>("盘点失败:"+e.getMessage());
}
}
代码逻辑简单来说就是,从mongo中取数据然后批量插入到数据库中。
初步分析认为,既然for循环一次会插入1w数据 那下一次循环的时候,这1w数据应该会释放内存当fullgc的时候这1w的数据引用肯定会被垃圾回收。但是为什么内存没有向下的波动呢。
3.本地复现(由于网络原因 无法远程监控生产jvm)
因此本地跑一下数据通过jvisualVM进行观察内存状况。(我通过junit进行单元测试)
理论方法执行完毕后应该会堆内存会下降。我这里睡眠是为了后边继续观察当前线程的监控情况。
看堆内存从开始执行的时候并不是有规律的波动,如红色框中。
在方法执行完毕后为何会居高不下 如蓝色框中。
看老年代中的内存也是一直在增加。
后来考虑到gc发生是需要特定情况的为了更好的观察我手动gc更好利于分析。(上面代码中注释放开 每次for循环以后gc一次, 代码执行完毕后gc一次)
2.改完之后再次进行运行进行观察。
有明显的堆内存回收 但是感觉每次回收都回收不全,由于生产数据量很大所以最终还是有可能导致占满内存。那么现在的问题就是找到为什么每次gc为什么还有部分没用回收 感觉像是什么东西在逐渐叠加。而且在方法执行完毕后的gc还是没有完全回收。
蓝色框是junit睡眠20s后的gc 但是这个gc完后还是占用了很大的内存。
4.解决
将上面的堆信息转储。然后用mat进行打开分析
发现 druid 里面有个实例占用内存占了77.7%
一查原来发现可能是druid用的有问题 果断关闭之后再次运行程序观察。
符合预期完美解决。
5.结论
一个配置的错误饶了一大圈 说明还是对配置和底层不熟,不过只有遇到问题才能进步。