天天看點

Excle導出優化(poi)

搜尋詞條

1、idea報java.lang.OutOfMemoryError: Java heap space怎麼解決?

2、java.lang.OutOfMemoryError: GC overhead limit exceeded怎麼解決?

3、xssfworkbook導出Excel記憶體溢出?

4、如何檢視jvm記憶體使用情況?

背景:使用POI導出海量資料記憶體溢出問題

應用配置:idea+tomcat7+informix+jdk7+poi3.9

本地機器的實體記憶體為8G,沒有對JVM進行配置。

場景:從資料庫查詢大批量資料(其實就2萬條資料),List存放2萬條資料,利用poi,通過自己寫的Excle資料導出工具,對資料進行處理,并以Excel檔案的形式進行導出(會建立1個或多個sheet頁)。建立Excle調用的方法如下(Utils中有Excle上傳及導出工具,此處不再粘貼代碼):

XSSFWorkbook wb = new XSSFWorkbook();      

在導出Excle檔案時,出現下述異常:

異常一:

嚴重: Servlet.service() for servlet [springMVC] in context with path [] threw exception [Handler dispatch failed; nested exception is

java.lang.OutOfMemoryError: Java heap space] with root cause

java.lang.OutOfMemoryError: Java heap space

在JVM中如果98%的時間是用于GC且可用的 Heap size 不足2%的時候将抛出此異常資訊。JVM堆的設定是指java程式運作過程中JVM可以調配使用的記憶體空間的設定。

JVM在啟動的時候會自動設定Heap size的值,其初始空間(即-Xms)是實體記憶體的1/64,最大空間(-Xmx)是實體記憶體的1/4。

可以利用JVM提供的-Xmn -Xms -Xmx等選項可進行設定。如果Heap Size設定偏小,除了這些異常資訊外,還會發現程式的響應速度變慢了。GC占用了更多的時間,

而應用配置設定到的執行時間較少。

Heap Size 最大不要超過可用實體記憶體的80%,一般的要将-Xms和-Xmx選項設定為相同,而-Xmn為1/4的-Xmx值。

Heap size的 -Xms -Xmn 設定不要超出實體記憶體的大小。否則會提示“Error occurred during initialization of VM Could not reserve enough space for object heap”。      

當未對JVM進行配置時,出現異常一,原因如下:Excle工具采取poi架構對Excle進行導出導緻的JVM記憶體溢出。原因是所建立的book sheet row cell 等,此時是存在記憶體的,并沒有 持久化,那麼随着資料量增大記憶體的需求量也就增大,那麼很大可能就是要 OOM了。而我本地并未對JVM進行配置,均采用的預設配置。當導出2萬條資料時,就導緻了記憶體溢出。(測試了下,在準備金系統的開發環境也有該問題,測試環境與生産環境還未出現OOM問題(記憶體足夠))。

然後我對JVM進行了配置(JVM子產品會對JVM調優進行詳細說明):

-Xms512m -Xmx2048m -Xss1024K      

在進行導出時,出現了下述異常:

異常二:
java.lang.OutOfMemoryError: GC overhead limit exceeded

出現該錯誤的原因是因為垃圾回收為了釋放較小的空間而占用了大量時間造成的。通常來說,當程式用98%的時間回收了不到2%的堆記憶體時導緻的。通常是設定的堆記憶體

太小,導緻沒有足夠的記憶體。

解決方法

1、首先檢查程式有沒有死循環或者其他一些導緻記憶體被大量占用的程式,如果确定程式沒有問題,隻是程式本身需要大記憶體時,通過設定增加記憶體。

2、添加jvm啟動參數限制使用記憶體:-XX:UseGCOverheadLimit

方法:找到tomcat部署路徑下./bin/catalina.sh檔案,打開,并在

# OS specific support. $var _must_ be set to either true or false.

cygwin=false

的上面添加(分兩種情況)      
1> 在java1.8之前的版本中

JAVA_OPTS="-Xms512m -Xmx2048m -Xss1024K -XX:PermSize=256m -XX:MaxPermSize=512m -XX:-UseGCOverheadLimit"

2> java1.8版本

JAVA_OPTS="-Xmx12000m -XX:-UseGCOverheadLimit"

這是一種應付了事的解決方案, 就是不想抛出 “java.lang.OutOfMemoryError: GC overhead limit exceeded” 錯誤資訊。

// 不推薦

-XX:-UseGCOverheadLimit

我們強烈建議不要指定該選項: 因為這不能真正地解決問題,隻能推遲一點 out of memory 錯誤發生的時間,到最後還得進行其他處理。指定這個選項, 會将原來的
java.lang.OutOfMemoryError: GC overhead limit exceeded 錯誤掩蓋,變成更常見的 java.lang.OutOfMemoryError: Java heap space 錯誤消息。

需要注意: 有時候觸發 GC overhead limit 錯誤的原因, 是因為配置設定給JVM的堆記憶體不足。這種情況下隻需要增加堆記憶體大小即可。

在大多數情況下, 增加堆記憶體并不能解決問題。例如程式中存在記憶體洩漏, 增加堆記憶體隻能推遲産生 java.lang.OutOfMemoryError: Java heap space 錯誤的時間。

當然, 增大堆記憶體, 還有可能會增加 GC pauses 的時間, 進而影響程式的吞吐量或延遲。

如果想從根本上解決問題,則需要排查記憶體配置設定相關的代碼. 簡單來說,需要回答以下問題:

哪類對象占用了最多記憶體?

這些對象是在哪部分代碼中配置設定的。

要搞清這一點, 可能需要好幾天時間。下面是大緻的流程:

獲得在生産伺服器上執行堆轉儲(heap dump)的權限。“轉儲”(Dump)是堆記憶體的快照, 可用于後續的記憶體分析. 這些快照中可能含有機密資訊, 例如密碼、信用卡賬号等, 

是以有時候, 由于企業的安全限制, 要獲得生産環境的堆轉儲并不容易。

在适當的時間執行堆轉儲。一般來說,記憶體分析需要比對多個堆轉儲檔案, 假如擷取的時機不對, 那就可能是一個“廢”的快照. 另外, 每執行一次堆轉儲, 就會對JVM進行

一次“當機”, 是以生産環境中,不能執行太多的Dump操作,否則系統緩慢或者卡死,你的麻煩就大了。

用另一台機器來加載Dump檔案。如果出問題的JVM記憶體是8GB, 那麼分析 Heap Dump 的機器記憶體一般需要大于 8GB. 然後打開轉儲分析軟體(我們推薦Eclipse MAT , 

當然你也可以使用其他工具)。檢測快照中占用記憶體最大的 GC roots。詳情請參考: Solving OutOfMemoryError (part 6) – Dump is not a waste。 這對新

手來說可能有點困難, 但這也會加深你對堆記憶體結構以及 navigation 機制的了解。

接下來, 找出可能會配置設定大量對象的代碼. 如果對整個系統非常熟悉, 可能很快就能定位問題。運氣不好的話,就隻有加班加點來進行排查了。

參看連結:有詳細說明:https://blog.csdn.net/renfufei/article/details/77585294      

我進行了下述配置(下面會對如何對服務進行JVM配置進行詳細說明):

-Xms512m -Xmx2048m -Xss1024K -XX:PermSize=256m -XX:MaxPermSize=512m -XX:-UseGCOverheadLimit      

雖然偶爾可以導出成功,但确實,大多數情況下報異常一。這也證明了上述所述的原因。

我通過java自帶的jconcole工具檢視了當進行導出時,JVM占用記憶體的變化情況,如下圖:15:00之前是當資料量在1萬條左右時,多次導出,堆記憶體使用量在1G浮動,但仍可以正常進行導出。15:00以後是當導出的資料條數達到2萬後,當第一次啟動服務,并導出時,可能會導出成功一次,往後均OOM。15:30是一段時間沒進行導出操作後,對記憶體使用量才降下來的。(下面會詳細說明怎麼檢視JVM的記憶體使用量)

Excle導出優化(poi)

通過上述分析,可以得出結論,導緻記憶體溢出的原因,就是poi的Excel導出工具占用的記憶體太大。

解決方案

上述通過對異常的分析,我們找到了異常出現的原因,是poi在進行Excel導出時,占用了大量的記憶體空間。解決方案如下,兩種方案:

第一種:當資料量并不是特别大的時候,可以通過設定JVM的記憶體,導出大批量的資料。但是當資料量達到百萬後,隻有通過方案二來解決。

第二種:通過SXSSFWorkbook來導出。

XSSFWorkbook wb = new XSSFWorkbook();      

下面我們詳細的分析兩種解決方案。

方案一

上面已經有了,不再贅述,對參數進行簡單說明:

堆(Heap)和非堆(Non-heap)記憶體

按照官方的說法:“Java虛拟機具有一個堆,堆是運作時資料區域,所有類執行個體和數組的記憶體均從此處配置設定。堆是在Java虛拟機啟動時建立的。”“在JVM中堆之

外的記憶體稱為非堆記憶體(Non-heapmemory)”。可以看出JVM主要管理兩種類型的記憶體:堆和非堆。簡單來說堆就是Java代碼可及的記憶體,是留給開發人員使用

的;非堆就是JVM留給自己用的,是以方法區、JVM内部處理或優化所需的記憶體(如JIT編譯後的代碼緩存)、每個類結構(如運作時常數池、字段和方法資料)以及方法和構造方法的代碼都在非堆記憶體中。

堆記憶體配置設定

JVM初始配置設定的記憶體由-Xms指定,預設是實體記憶體的1/64;JVM最大配置設定的記憶體由-Xmx指定,預設是實體記憶體的1/4。預設空餘堆記憶體小于40%時,JVM就會增

大堆直到-Xmx的最大限制;空餘堆記憶體大于70%時,JVM會減少堆直到-Xms的最小限制。是以伺服器一般設定-Xms、-Xmx相等以避免在每次GC後調整堆的大小。

非堆記憶體配置設定

JVM使用-XX:PermSize設定非堆記憶體初始值,預設是實體記憶體的1/64;由XX:MaxPermSize設定最大非堆記憶體的大小,預設是實體記憶體的1/4。

JVM記憶體限制(最大值)

首先JVM記憶體限制于實際的最大實體記憶體(廢話!呵呵),假設實體記憶體無限大的話,JVM記憶體的最大值跟作業系統有很大的關系。簡單的說就32位處理器雖然可

控記憶體空間有4GB,但是具體的作業系統會給一個限制,這個限制一般是2GB-3GB(一般來說Windows系統下為1.5G-2G,Linux系統下為2G-3G),而64bit以上的處理器就不會有限制了。

主要通過以下的幾個jvm參數來設定堆記憶體的:

-Xmx512m 最大總堆記憶體,一般設定為實體記憶體的1/4

-Xms512m 初始總堆記憶體,一般将它設定的和最大堆記憶體一樣大,這樣就不需要根據目前堆使用情況而調整堆的大小了

-Xmn192m 年輕帶堆記憶體,sun官方推薦為整個堆的3/8

-Xss 為jvm啟動的每個線程配置設定的記憶體大小,預設JDK1.4中是256K,JDK1.5+中是1M

堆記憶體的組成 總堆記憶體 = 年輕帶堆記憶體 + 年老帶堆記憶體 + 持久帶堆記憶體

年輕帶堆記憶體 對象剛建立出來時放在這裡

年老帶堆記憶體 對象在被真正會回收之前會先放在這裡

持久帶堆記憶體 class檔案,中繼資料等放在這裡

-XX:PermSize=128m 持久帶堆的初始大小

-XX:MaxPermSize=128m 持久帶堆的最大大小,eclipse預設為256m。如果要編譯jdk這種,一定要把這個設的很大,因為它的類太多了。

關于JVM的調優及配置說明等等,網上的各種資料、部落格繁雜龐多,看的我是頭昏眼花,還有一大部分是搬運工,沒有自己做過配置,了解。這裡不再進行代碼搬運。關于JVM的調優應該是一個專門的課題來研究,下面我會自己看資料,寫專門的部落格。現在隻是解決我在項目中遇到的問題,不做深究。

方案二

使用SXSSFWorkbook 導出進行資料導出。詳見連結:https://www.cnblogs.com/zhangshuaivole/p/13058964.html

但是這種方式沒有自定義顔色的方式。

自己的Excel檔案導出工具,詳見連結:https://www.cnblogs.com/zhangshuaivole/p/13793392.html

延伸

1、頻繁full gc有什麼影響?(JVM調優子產品)

2、JVM 内置的通用垃圾回收原則。(JVM調優子產品)

3、如何配置JVM的記憶體。

https://www.cnblogs.com/vole/p/12059688.html

4、JVM調化。(JVM調優子產品)

5、導緻OOM的三種可能原因?

https://www.cnblogs.com/vole/p/12074372.html

6、如何檢視JVM的記憶體?

https://i-beta.cnblogs.com/posts/edit-done;postId=12083319