写点什么

POI 内存溢出故障排查

用户头像
Season
关注
发布于: 2020 年 07 月 23 日

发现问题

今天早上收到业务方反馈(尴尬),后台(gc-bootdo)非常慢,运维发现问题后,发现服务器CPU很高,Tomcat也在频繁FullGC,机智的运维同学第一时间dump tomcat的堆内存。

分析问题

从运维反馈的情况,应用服务器频繁FullGC,初步推断是堆内存满了且存在大量的对象无法被回收。以下通过使用MAT分析dump文件,具体步骤如下:

  • 导入dump文件到mat,并使用Leak Suspects进行分析:





  • 从上可以看出线程http-nio2-999-exec-78占用了大部分的本地变量,点击See stacktrace继续查看





  • 从线程栈信息我们已经可以初步定位到问题的入口了,进一步我们再看一下具体占内存的对象,点击下图左上方红框的小图标。





  • 其中ChatroomMessageDo为主要导出的业务对象,从上图可以看到,我们导出的对象实际个数为38w多,占用的堆内存大小为170m左右,那么问题出现在哪里呢?很明显应该是在导出功能使用的POI组件上,结合官方文档,我们可以看到官网建议在导出大量导出数据时,使用SXSSFWorkbook而不是XSSFWorkbook。



bootdo代码



官方文档截图



解决问题

从上述分析,我们使用XSSFWorkbook存在内存溢出问题,在解决问题之前,我们得有工具进行量化,记录优化前和优化后的指标,通过指标的对比,能够更好的体现优化效果。这里使用的量化工具是JUnit+IDEA+VisualVM(工具的使用就不细述了,自行百度安装)。

public class ExcelUtilTest {
List<ChatroomMessageDO> messageList;
@Before
public void setup() {
messageList = new ArrayList<>();
for (int i = 0; i < 1_000_000; i++) {
final ChatroomMessageDO m = new ChatroomMessageDO();
m.setLiveCode("80");
m.setLiveTitle("学习学习");
m.setUserCode(123456);
m.setUserNickName("hahaha");
m.setUserCode(1);
m.setUserTemporaryNum(45646);
m.setMsgType("adfdsf");
m.setContent("hello, 你好");
m.setSendTimeStr("2020-06-01 00:00:00");
messageList.add(m);
}
}
@Test
public void testExportExcelHeadersAndFieldNames() throws FileNotFoundException {
ExcelUtil<ChatroomMessageDO> exportExcel = new ExcelUtil<>();
File file = new File("/tmp/test_out.xls");
file.delete();
FileOutputStream os = new FileOutputStream(file);
String headers[] = new String[]{"直播间编号", "直播间标题", "用户编号", "用户昵称", "用户状态", "用户禁言次数", "消息类型", "消息内容",
" 发送时间"};
Integer widths[] = new Integer[]{20, 40, 20, 20, 20, 20, 20, 60, 20};
String fieldNames[] = new String[]{"liveCode", "liveTitle", "userCode", "userNickName", "userState",
"userTemporaryNum", "msgType", "content", "sendTimeStr"};
exportExcel.exportExcelHeadersAndFieldNames(null, headers, widths, fieldNames, messageList,
os);
}
}



通过Run With VisualVM执行以上测试代码,可以从VisualVM中获得对应的堆栈信息

优化前(导出20w数据):





从上图可以看出,JVM堆栈的大小是随着导出的数据量(写入excel的数据量)线性增长的,堆最大使用了2G左右。

优化后(导出20w数据):





我们将SSFWorkbook改为XSSFWorkbook后,得出以上测试结果,堆最大的使用大小为860m左右。那么问题来了,修改后的堆大小是否还会随着数据量的大小接着线性的增长呢?我们把测试数据量加到100w试试。



优化后(导出100w数据):





可以看到内存占用的大小并不会随着数据量的增大而线性增大,随着垃圾回收,堆内存的使用大小逐渐减少。

结论

在导出数据量比较大的数据时应使用SXSSFWorkbook,并配置好缓冲大小。

SXSSFWorkbook wb = new SXSSFWorkbook(100);
Sheet sh = wb.createSheet();



参考:http://poi.apache.org/components/spreadsheet/how-to.html#sxssf



发布于: 2020 年 07 月 23 日阅读数: 55
用户头像

Season

关注

还未添加个人签名 2019.09.28 加入

还未添加个人简介

评论

发布
暂无评论
POI内存溢出故障排查