第一次報障
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,降低內存開銷。
參考文檔
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;
}