背景:线上报表导出 跑了没1分钟堆内存溢出,emmm
需求分析
1、数据存储在es,都是单表 涉及表a,b,c,d,e,f 6张表
2、每张表数据在百万以上,每个表取几个字段 拼接成一张大报表
3、6张表之间对应关系,可能 1条a数据 几千条b数据
痛点
1、springboot 服务内存 256, new 对象太快 太多 没有足够的空间挪动回收
解决:
1、利用线程池+redis 异步执行、
poolTaskExecutor.execute(new Runnable() {
@Override
public void run() {
try{
//redis 加锁
redisCommonService.set(redisKey,CommonConst.IS_NO);
log.info("子线程执行完毕。。。");
}catch (Exception e){
log.error("生成报表数据异常",e);
}finally {
//释放锁
redisCommonService.set(redisKey,CommonConst.IS_YES,6000L);
}
}
});
分析:一个子线程慢慢跑,跑一天都行 redis保证一个用户导出只能跑一个线程,避免子线程太多 导致oom
2、及时释放内存,使用 clear 和弱引用
public static void batchSave(List<Map<String, Object>> list,Class c){
log.info("总共 需要保存数据有:"+list.size());
//分次批量插入数据,如果不分次,则可能数据太多导致插入不成功
int allSize = list.size(); //总记录数
int perSize = 500; //每次插入的记录数
int len = 0; //插入的次数
if (allSize % perSize == 0) { //如果
// 没有余数
len = allSize / perSize;
} else {
len = allSize / perSize + 1;
}
List<Map<String, Object>> tempList = null;
for (int i = 0; i < len; i++) {
int startNum = i * perSize;
int endNum = startNum + perSize;
tempList = new ArrayList<>();
WeakReference<Object> obj=new WeakReference<>(tempList);
for (int index = startNum; index < endNum && index < allSize; index++) {
tempList.add(list.get(index));
}
log.info("本次 是第"+i+"页,批量保存数据有:" + tempList.size());
boolean saveDataBatch = coreService.saveDataBatch(tempList);
log.info("批量保存返回结果:" + saveDataBatch);
tempList.clear();
}
tempList=null;//htlp gc
}
分析
1、tempList.clear(); 集合循环利用,避免new 太多
2、WeakReference 加快回收
弱引用测试demo
private static void test3(){
Object obj1=new Object();
WeakReference<Object> obj=new WeakReference<>(obj1);
obj1=null; //help gc
//内存够用,手动 gc
System.gc();
//内存不够用,自动 gc
System.out.println(obj1);
System.out.println(obj.get());
}
3、由于 数据量大,每个操作分页查 分页存,一批500、存入单表即可
通过以上措施,成功避免了oom、生成30万报表大概 30分钟。___…