1. 程式人生 > >由excel匯出引起的cpu 100% 和gc 的問題

由excel匯出引起的cpu 100% 和gc 的問題

大家好,我是烤鴨:
    記一次 由excel匯出 導致的cpu飆升200%,jvm 記憶體不足。

1.  場景復現


    前端頁面匯出Excel,之前匯出4,5W條資料都沒什麼問題的。
    今天業務突然反饋說匯出不了了,我試著匯出了2w資料,發現頁面卡住了,
    沒有響應了,查日誌,報錯如下。

java.lang.IllegalStateException: Cannot call sendRedirect() after the response has been committed
	at org.apache.catalina.connector.ResponseFacade.sendRedirect(ResponseFacade.java:488)
	at javax.servlet.http.HttpServletResponseWrapper.sendRedirect(HttpServletResponseWrapper.java:138)

     查看了匯出方法,發現如果repsonse在響應過程中丟擲異常的話,就會有類似的問題。

     
     正常的話,因為需要把錯誤資訊帶到頁面,catch之後再return到對應的頁面。
     但如果是上圖所示的write方法,如果這地方報異常,就會出現
     Cannot call sendRedirect() after the response has been committed

的異常。

     輸出檔案的程式碼如下,如果這時候異常。

/**
	 * 輸出到客戶端
	 * @param fileName 輸出檔名
	 */
	public ExportExcel write(HttpServletResponse response, String fileName) throws IOException{
		response.reset();
        response.setContentType("application/octet-stream; charset=utf-8");
        response.setHeader("Content-Disposition", "attachment;fileName=" + new String(fileName.getBytes("GBK"), "iso-8859-1")); //中文檔名處理
        write(response.getOutputStream());
		return this;
	}

    知道這是寫法的問題,但是並沒有什麼好的辦法解決。
    因為 異常需要通過addMessage(redirectAttributes, e.getMessage()) 帶到重定向的頁面。

    但是轉念一想,這個問題不應該在這地方出現,因為之前用了很長時間是沒有這個問題的。

2.    尋找問題

    再次執行匯出的時候,監測了一下cpu,為啥,第六感吧。

top    #檢視cpu程序執行
ps -ef|grep tomcat_xxx    #檢視tomcat 的 pid
	

  pid 是 19021

  發現 19021 的程序爆表了...如下圖

  

jstat -gcutil 19021 5000    #每隔5秒列印一次gc  

 又去看了gc的情況,每隔5秒列印一次gc,各個空間全都滿了。而且YGC和FGC的頻次和時間在迅速增加。(下圖中的時間還沒到滿的時候,後來確實都100了)

還好,伺服器還撐得住,應該是資料量不是特別大,沒有報java.lang.OutOfMemoryError。

jmap -heap 19021    #檢視堆記憶體詳細資訊
jstack 19021    #檢視執行緒問題(是否死鎖)

  其實我的問題在這就解決了,想了想最近改動過的程式碼,基本就鎖定了問題,匯出時候的問題,
  後來查了excel工具類的程式碼提交,發現在建立cell單元格的時候,沒有判斷樣式是否存在,
  只要是空單元格,就新建立一個單元格樣式。
  改之前:

CellStyle style = wb.createCellStyle();
cell.setCellStyle(style);

  改之後:

    if (val != null){
		CellStyle style = styles.get("data_column_"+column);
		if (style == null){
			style = wb.createCellStyle();
			style.cloneStyleFrom(styles.get("data"+(align>=1&&align<=3?align:"")));
	        style.setDataFormat(wb.createDataFormat().getFormat(cellFormatString));
			styles.put("data_column_" + column, style);
		}
		cell.setCellStyle(style);
	}

    不要小瞧了這個判空,原來的寫法會多建立2w行*20列=40w個物件,導致各個空間的滿了原因。
    如果還沒找到問題的話,就輸出gc日誌,慢慢查。

jmap -histo 9021>xxx.log    #輸出gc日誌到檔案

    
3.    總結

    如果是gc或者記憶體問題。

    獲取當前程序pid

    ps -ef|grep tomcat_xxx 或者 ps -ef|grep java 或者 jps
    檢視cpu利用率 
    top
    監測gc
    jstat -gcutil pid 5000
    檢視堆記憶體詳細資訊
    jmap -heap pid
    檢視是否死鎖情況
    jstack pid
    還找不到的話,輸出gc的日誌慢慢找
    jmap -histo pid>xxx.logs