OOM发生后,我们可以通过堆转储文件分析出原因,那么我们能从堆转储文件里挖到更多细节吗?这一篇我们继续聊聊Excel OOM的事儿。
1 理论基础
根据MAT文档Analyzing Threads描述,hprof文件含有线程的栈以及栈里的局部变量:
Some heap dump formats (e.g. HPROF dumps from recent Java 6 VMs and IBM system dumps) contain information about the call stacks of threads, and the Java local objects per stack frame.
也就是说若程序实现是把excel解压缩之后的内容都加载到内存中的话,那么是可能提取出来的。
只要能找到内容,根据上文说到的xlxs结构,我们可以把xml等文本写到文本,再根据响应的文件结构存放,使用zip压缩后更名为xlsx文件,就算是提取了。
2 阅读源码
走一下POI的初始化workbook代码,可以发现构造器org.apache.poi.xssf.usermodel.XSSFWorkbook#XSSFWorkbook(org.apache.poi.openxml4j.opc.OPCPackage)
的唯一入参的类型OPCPackage唯一实现是org.apache.poi.openxml4j.opc.ZipPackage
,也许会有解压缩后的文件内容。
3 查看栈的局部变量
打开MAT的线程视图:
打开堆转储文件,根据之前的分析OOM是由线程http-nio-8080-exec-5
引发的,我们导航到这个线程;点击以下按钮,展开这个线程的全部栈帧:
然后导航到方法org.apache.poi.xssf.usermodel.XSSFWorkbook#XSSFWorkbook(org.apache.poi.openxml4j.opc.OPCPackage):
关注ZipPackage类型的变量,点击它左侧的按钮展开,再递归地展开,最终能找到字段zipEntries
,对应的列表既是xlsx文件内包含的所有文件:
4 提取内容到xlsx文件
找到了xml数据,我们如何构造一个xlsx文件呢,根据上文提到的xlsx文件结构:
.
├── [Content_Types].xml
├── _rels
├── docProps
│ ├── app.xml
│ └── core.xml
└── xl
├── _rels
│ └── workbook.xml.rels
├── sharedStrings.xml
├── styles.xml
├── theme
│ └── theme1.xml
├── workbook.xml
└── worksheets
└── sheet1.xml
看起来是比较复杂的,我们可以逆向思维:创建一个空白的xlsx,解压,然后将堆中的xml内容覆盖到对应的xml文件,再打包成zip,改拓展名为xlsx
打开Excel,新建空白的工作簿,在第一张表的某个单元格输入内容,保存到文件my-xlsx.xlsx
,然后解压:
回到MAT界面,右击xl/sharedStrings.xml
的data
属性,Copy > Save Value To File:
覆盖到我们刚才解压的xlsx的sharedStrings.xml:
存储后确认对应文件的长度是否于堆内的长度一致:
重复上面的动作,将xl/worksheets/sheet1.xml
的内容覆盖到文件。
更完善的处理应该是将堆里的10个文件都提取出来,但只是为了查看第一张表的内容,提取这里两个文件已经足够。
然后打开命令行,切换到my-xlsx文件夹,当前文件夹内容如下:
➜ my-xlsx tree .
.
├── [Content_Types].xml
├── _rels
├── docProps
│ ├── app.xml
│ └── core.xml
├── my-new.xlsx
└── xl
├── _rels
│ └── workbook.xml.rels
├── sharedStrings.xml
├── styles.xml
├── theme
│ └── theme1.xml
├── workbook.xml
└── worksheets
└── sheet1.xml
6 directories, 10 files
执行命令zip -r my-new.xlsx *
:
➜ my-xlsx zip -r my-new.xlsx *
adding: [Content_Types].xml (deflated 71%)
adding: _rels/ (stored 0%)
adding: _rels/.rels (deflated 60%)
adding: docProps/ (stored 0%)
adding: docProps/app.xml (deflated 51%)
adding: docProps/core.xml (deflated 50%)
adding: xl/ (stored 0%)
adding: xl/workbook.xml (deflated 61%)
adding: xl/worksheets/ (stored 0%)
adding: xl/worksheets/sheet1.xml (deflated 91%)
adding: xl/styles.xml (deflated 60%)
adding: xl/theme/ (stored 0%)
adding: xl/theme/theme1.xml (deflated 80%)
adding: xl/_rels/ (stored 0%)
adding: xl/_rels/workbook.xml.rels (deflated 66%)
adding: xl/sharedStrings.xml (deflated 48%)
可以发现当前文件夹内新增了文件my-new.xlsx
,使用Excel打开它:
因为这个xlsx文件的构造过程不完善,所以会提示,我们选择Yes继续:
然后Excel会提示已经修复了这个文件,若选择View则可查看修复日志,选择Delete则直接查看修复之后的内容:
查看修复之后的内容:
至此提取成功。
5 总结
以上,我认识到了堆转储文件的更多功能,学习了更多排查问题的思路和方法。