假设导出百万级数据到Excel时出现频繁Full GC,如何定位内存泄漏点?如果发现是POI的SXSSFWorkbook未正确释放资源,除了调用dispose(),还有哪些优化手段?请结合JVM参数调优和代码规范说明
可通过JVisualVM 分析工具,分析堆栈内存
1 | 启动参数添加GC日志和堆转储 |
打开dump.hprof,查看Dominator Tree,找到占用内存最大的对象。
若发现大量SXSSFWorkbook或Row/Cell对象未被回收,说明存在资源未释放
1 | [Full GC (Allocation Failure) ... |
现象:老年代内存几乎无回收,表明存在长期存活的大对象
除了dispose()外,需结合以下措施:
- 1.分页写入与流式处理分页加载:减少单次内存占用(参考您简历中的分页查询优化)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23// 分页查询数据(避免一次性加载百万数据到内存)
int pageSize = 10000;
for (int page = 0; ; page++) {
List<Data> batch = queryData(page, pageSize);
if (batch.isEmpty()) break;
writeBatchToExcel(batch);
}
// 使用SXSSFWorkbook的滑动窗口机制
SXSSFWorkbook workbook = new SXSSFWorkbook(100); // 保留100行在内存
try {
Sheet sheet = workbook.createSheet("Data");
for (Data data : batch) {
Row row = sheet.createRow(currentRow++);
// 填充单元格...
if (currentRow % 100 == 0) {
((SXSSFSheet) sheet).flushRows(100); // 强制刷新到磁盘
}
}
} finally {
workbook.dispose();
workbook.close();
}
滑动窗口:通过flushRows()及时释放内存中的行数据 - 2.及时清空大对象,帮助GC回收
1
2
3List<Data> batch = queryData(page, pageSize);
writeBatchToExcel(batch);
batch.clear(); // 帮助GC回收 - 3.文件流强制释放
1
2
3
4
5
6
7try (FileOutputStream fos = new FileOutputStream("report.xlsx")) {
workbook.write(fos);
} catch (IOException e) {
// 异常处理
} finally {
Files.delete(workbook.getTempFile()); // 删除临时文件
} - 4.JVM调优建议
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15总堆大小(根据物理内存调整)
-Xmx8g -Xms8g
年轻代占比(增大Survivor区避免过早晋升)
-XX:NewRatio=1 # 年轻代:老年代=1:1
-XX:SurvivorRatio=6 # Eden:Survivor=6:1:1
-XX:MaxTenuringThreshold=15
G1垃圾回收器优化
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200 # 目标停顿时间
-XX:G1ReservePercent=20 # 预留内存防晋升失败
-XX:InitiatingHeapOccupancyPercent=45 # 更早启动并发标记
监控与诊断
-XX:+G1PrintHeapRegions # 跟踪Region分配
-XX:+PrintAdaptiveSizePolicy # 查看G1自动调整策略