1、执行用例总是提示获取到 dataprovider 获取到的 Object[][] 为空
2、在本地调试并不能每次复现,重启一下服务器就能好。没有明显的报错日志。
1、运行调试,发现总是读取表格文件的这一步
发现这里的错误只捕获了 IOException。于是在抛出错误的时候,将 IOException 调整为 Exception
一开始捕捉到的是一个 NullPointException
于是第一次的解决方案是:加多一个 NullPointException。
由于本地运行正常,所以就上线了并告诉大家修复了问题。
上线后,测试同学小美仍然反馈出执行不了的问题,然后重启之后,原本的文件就能够正常执行了,但是再上传一个新文件执行,同样会报这个错。
1、此时发现测试同学小美所使用的表格文件特别大。正常情况下只有 200k 左右的用例文件,执行有问题的文件居然高达 4M。
考虑可能是 4M 的文件解析带来的问题,
于是在抛出错误的时候,再次调整为 Exception
1、先执行一次 4M 的文件的用例
2、执行完成后再上传一个 4M 的用例(上传用例过程在线上执行,直连线上的数据库,然后复制一份用例文件到本地执行)
3、使用新的文件执行用例
此时明确发现,是由于 OOM 内存溢出导致的问题
于是打开 jprofiler 进行分析
1、第一次运行 4m 文件
再上传一个新的 4m 文件,然后执行用例(上传用例过程在线上执行,直连线上的数据库,然后复制一份用例文件到本地执行)。
直接内存占用高达 1.62G,GC100% 运行,CPU 未出现过载,所以性能瓶颈点主要在内存。判断可能存在内存泄漏。
另外还发现:超时还可能导致无法连接上 zk,导致后续的测试无法继续执行
[图片上传失败...(image-5f70f9-1548245913902)]
但从上面只能定位到可能是内存泄漏,但未能定位到是什么原因导致,于是我们转向 Live Memory
打开 jprofiler 的 Live Memory,执行如下步骤:
1、先执行两个普通大小用例文件的用例,观察内存中那类占用比较多 -- 此时没发现什么异常(所以没有截图)
2、再执行一个 4M 大小的用例。
发现下图中 AttrXobj、ElementXobj 暴涨。几乎占据了图中 80% 以上的量。
(后续还执行了第二个 4M 文件,但由于直接跑崩了所以没有收集到第二次 4M 文件执行的内存数据)
然后搜索 AttrXobj(搜索 AttrXobj),得知该类与 XSSFWorkbook 有关。
并且
1、从第一条搜索结果可知(SXSSFWorkbook & XSSFWorkbook 效率比拼)XSSFWorkbook 本身在大数据量下存在性能问题。
2、在第四条记录上看到了非常类似情况:记一次 FullGC 的排查
所以基本上定位到问题是处在我们 ExcelUtils 中用到的 XSSFWorkbook 对象身上。
第一个:像记一次 FullGC 的排查这样限制用户上传文件大小
思考:虽然该方法成本最低。但由于用例文件可能会存放多个 sheet,即使单个 sheet 数据量不超过 100k,整个 excel 文件的总 size 无法预估,且无法解决根本问题,所以先不采取这个方法。
第二个:检查内存泄漏点,解决内存泄漏
从下图中可发现,黄框部分是执行完 GC 之后的内存占用情况,随着用例执行而增多。
结合两图可以定位到是 XSSFWorkbook 的问题。那我们关注到调用这个类的方法上。
从下图中可以看到,在方法中,有一个 FileInputStream 对象,但是读取了之后没有对应的关闭方法。
excelWBook、excelWsheet 这两个对象是一个静态对象,在执行完操作之后,却没有进行对象的清空。可能会一直存活着。
从代码里也可以看到一个点:excelWBook 这个变量是一个静态变量,第一次读取 4M 文件后,未被释放。然后到了第二次读取的时候,
excelWBook = new XSSFWorkbook(ExcelFile)
先执行 new XSSFWorkbook(ExcelFile) 再将 new 出来的对象赋值给 excelWBook。所以 此时 excelWBook 占用一波内存,new XSSFWorkbook(ExcelFile) 又要占用一波内存,导致直接内存爆掉了。
1、将 excelWSheet、excelWBook、cell 从静态变量改为实例变量,释放内存。
2、加一个 try-finally/try-with-resource 关闭 FileInputStream 文件流。
3、使用 SXSSFWorkbook 替换 XSSFWorkbook,降低内存开销。
IntelliJ IDEA 集成 JProfiler,入门教程
SXSSFWorkbook & XSSFWorkbook 效率比拼
多次执行 4M 大小的文件响应正常且内存稳定。
修复后的关键源码
public static Object[][] getTableArray(String FilePath, String SheetName, int totalCols, Boolean isWithTitleRow) throws IOException {
String[][] tabArray = (String[][])null;
FileInputStream excelFile = null;
try {
excelFile = new FileInputStream(FilePath);
XSSFWorkbook excelWBook = new XSSFWorkbook(excelFile);
XSSFSheet excelWSheet = excelWBook.getSheet(SheetName);
int startRow = 0;
int startCol = 0;
int lastRowNum = excelWSheet.getLastRowNum();
int lastCol = totalCols - 1;
if (isWithTitleRow) {
startRow = 1;
}
int totalRow = getRealRowNum(excelWSheet, startRow, lastRowNum, startCol, lastCol);
if (isWithTitleRow) {
tabArray = new String[totalRow][totalCols];
} else {
tabArray = new String[totalRow + 1][totalCols];
}
for(int currentRow = startRow; currentRow <= totalRow; ++currentRow) {
for(int currentCol = startCol; currentCol <= lastCol; ++currentCol) {
tabArray[currentRow - startRow][currentCol] = getCellData(excelWSheet, currentRow, currentCol);
}
}
} finally {
if (excelFile != null) {
excelFile.close();
}
}
return tabArray;
}