天天看點

JVM記憶體模型以及垃圾收集政策解析【續】

今天接着補全上篇博文。

三 垃圾收集政策配置

吞吐量是指GC的時間與運作總時間的比值,比如系統運作了100分鐘,而GC占用了一分鐘,那麼吞吐量就是99%,吞吐量優先一般運用于對響應性要求不高的場合,比如web應用,因為網絡傳輸本來就有延遲的問題,GC造成的短暫的暫停使得使用者以為是網絡阻塞所緻。

吞吐量優先可以通過-XX:GCTimeRatio來指定。

當通過-XX:GCTimeRatio不能滿足系統的要求以後,我們可以更加細緻的來對JVM進行調優。

首先因為要求高吞吐量,這樣就需要一個較大的Young generation,此時就需要引入“Parallel scavenging Collector”,可以通過參數:-XX:UseParallelGC來配置。

 java -server -Xms3072m -Xmx3072m -XX:NewSize=2560m -XX:MaxNewSize=2560 XX:SurvivorRatio=2 -XX:+UseParallelGC 

當年輕代使用了"Parallel scavenge collector"後,老生代就不能使用"CMS"GC了,在JDK1.6之前,此時老生代隻能采用串行收集,而JDK1.6引入了并行版本的老生代收集器,可以用參數-XX:UseParallelOldGC來配置。

預設情況下,Parallel scavenging Collector 會開啟與cpu數量相同的線程進行并行的收集,但是也可以調節并行的線程數。假如你想用4個并行的線程去收集Young generation的話,那麼就可以配置-XX:ParallelGCThreads=4,此時JVM的配置參數如下:

 java -server -Xms3072m -Xmx3072m -XX:NewSize=2560m -XX:MaxNewSize=2560 XX:SurvivorRatio=2 -XX:+UseParallelGC -XX:ParallelGCThreads=4

在采用了"Parallel scavenge collector"後,此GC會根據運作時的情況自動調節survivor ratio來使得性能最優,是以"Parallel scavenge collector"應該總是開啟此參數。

此時JVM的參數配置如下:

java -server -Xms3072m -Xmx3072m -XX:+UseParallelGC    -XX:ParallelGCThreads=4 -XX:+UseAdaptiveSizePolicy

響應時間優先是指GC每次運作的時間不能太久,這種情況一般使用與對及時性要求很高的系統,比如股票系統等。

響應時間優先可以通過參數-XX:MaxGCPauseMillis來配置,配置以後JVM将會自動調節年輕代,老生代的記憶體配置設定來滿足參數設定。

在一般情況下,JVM的預設配置就可以滿足要求,隻有預設配置不能滿足系統的要求時候,才會根據具體的情況來對JVM進行性能調優。如果采用預設的配置不能滿足系統的要求,那麼此時就可以自己動手來調節。

此時"Young generation"可以采用"Parallel copying collector",而"Old generation"則可以采用"Concurrent Collector",

舉個例子來說,以下參數設定了新生代用Parallel Copying Collector,老生代采用CMS收集器。

java -server -Xms512m -Xmx512m  -XX:NewSize=64m -XX:MaxNewSize=64m -XX:SurvivorRatio=2         -XX:+UseConcMarkSweepGC -XX:+UseParNewGC 

此時需要注意兩個問題:

1 如果沒有指定-XX:+UseParNewGC,則采用預設的非并行版本的copy collector.

2  如果在一個單CPU的系統上設定了-XX:+UseParNewGC ,則預設還是采用預設的copy collector.

預設情況下,Parallel copy collector啟動和CPU數量一樣的線程,也可以通過參數-XX:ParallelGCThreads來指定,比如你想用3個線程去進行并發的複制收集,那麼可以改變上述參數如下:

java -server -Xms512m -Xmx512m -XX:NewSize=64m  -XX:MaxNewSize=64m -XX:SurvivorRatio=2        -XX:ParallelGCThreads=4    -XX:+UseConcMarkSweepGC       -XX:+UseParNewGC 

預設情況下,CMS gc在"old generation"空間占用率高于68%的時候,就會進行垃圾收集,而如果想控制收集的臨界值,可以通過參數:-XX:CMSInitiatingOccupancyFraction來控制,比如改變上述的JVM配置如下:

java -server -Xms512m -Xmx512m -XX:NewSize=64m  -XX:MaxNewSize=64m -XX:SurvivorRatio=2        -XX:ParallelGCThreads=4    -XX:+UseConcMarkSweepGC       -XX:+UseParNewGC      -XX:CMSInitiatingOccupancyFraction=35

四 GC觸發以及常見的記憶體錯誤

Minor GC主要負責收集Young Generation,Minor GC一般在新生代不夠用的情況下觸發,比如我們一次性建立了很多對象等。

List<byte[]> buffer = new ArrayList<byte[]>(); for(int i=0;i<8*1024;i++){

buffer.add(new byte[1024]);

}

以上代碼通過一個位元組數組的List模拟觸發Minor gc,設定JVM參數如下:

-verbose:gc -Xmn10M -Xms64M -Xmx64M -XX:+PrintGC

設定以上參數以後,因為-Xmn=10M,預設-XX:SurvivorRatio=8 ,則eden的空間大小為8M,當eden對象大小超過8M的時候就會觸發Minor gc.

運作的結果如下:

[GC 8192K->8030K(64512K), 0.0243391 secs]

從運作結果可以看出,gc前和gc後的eden區的占用情況,需要注意的是括号裡(64512)這個數值時63M,它不包括一塊Survivor 空間。

這裡需要注意的一點就是,如果建立的對象大于eden的大小,那麼将不會通過Survivor空間複制,直接轉移到old generation.

調整以上代碼如下:

List<byte[]> buffer = new ArrayList<byte[]>();

buffer.add(new byte[8*1024*1024]);

通過同樣的JVM參數運作,則發現不會觸發Minor gc,這是因為對象超過了eden的大小,進而直接配置設定到了Old generation.

Old generation 空間滿是因為Young generation提升到Old generation的對象+Old generation的本來的大小已經接近或者超過了Old generation的大小。對于CMS GC,當Old generation空間使用率接近某一個比例,可以通過參數-XX:CMS InitialingOccupancyFraction,此參數表示Old generation的使用率,預設為68%。

Young generation對象提升到Old generation對象有以下三種情況:

Ø 配置設定的對象大于eden空間的大小

Ø 在Young generation代中經過了-XX:MaxTenuringThreshold次複制任然存活的對象

Ø Minor gc的時候,放不進to survivor的對象

當Major GC以後,如果還沒有足夠的空間可以用的話,此時就會抛出java.lang.OutOfMemory:java heap space,當出現此錯誤的時候,說明可能存在記憶體洩露現象的,這時候就需要我們對程式進行檢檢視看什麼地方存在記憶體洩露的。

我們可以通過以下代碼來模拟一下java.lang.OutOfMemory:java heap space的發生:

buffer.add(new byte[10*1024*1024]);

以上代碼配置設定了一個10M的位元組數組,我們通過以下的參數運作:

-verbose:gc -Xmn10M -Xms20M -Xmx20M -XX:+PrintGC

以上參數指定Young generation的空間大小為10M,Old generation空間大小為10M。

運作結果如下:

[GC 327K->134K(19456K), 0.0056516 secs]

[Full GC 134K->134K(19456K), 0.0178891 secs]

[Full GC 134K->131K(19456K), 0.0141412 secs]

Exception in thread "main" java.lang.OutOfMemoryError:   Java heap space

        at Test.main(Test.java:30)

從運作結果可以看出,JVM進行了一次Minor gc和兩次的Major gc,從Major gc的輸出可以看出,gc以後old區使用率為134K,而位元組數組為10M,加起來大于了old generation的空間,是以抛出了異常,如果調整-Xms21M,-Xmx21M,那麼就不會觸發gc操作也不會出現異常了。

Perm Generation空間主要存放Class對象,Field,Method對象,當一次性加載太多的類或者在熱部署以後不解除安裝類的情況(比如在Jboss伺服器中,如果經常熱部署一些應用就會出現Perm 空間溢出)就會造成Perm Generation被占滿,此時就會出現:

java.lang.OutOfMemory:PermGen space,在出現此異常的時候,如果是因為熱部署引起的,我們重新啟動AS就可以了,如果是因為加載的類太多,此時可以通過-XX:PermSize和-XX:MaxPermSize調整。

java.lang.StackOverflowError錯誤表示JVM棧溢出,出現這個錯誤的原因一般都是遞歸的層次太深,或者無限的遞歸造成的。出現這種錯誤的時候首先要對應用程式進行檢查,看看是那些代碼造成了棧溢出,如果是遞歸造成的可以改為疊代方式實作。

JVM同樣也提供了一個參數來讓我們調節運作時棧空間的大小。-XX:Xss=256K表示棧空間最大為256K.我們也可以調大,但是建議不要對此參數進行調節。

java.lang.OutOfMemoryError: Java heap space這個錯誤表示JVM的新生代和老生代的記憶體不足。出現這個錯誤說明應用程式出現了記憶體溢出或者程式所需要的記憶體大于JVM的記憶體設定了。

遇到這個問題的時候,首先我們可以調節JVM的Heap記憶體的大小,具體可以通過-Xmx -Xms來進行設定,如果設定大以後還是會出現記憶體溢出,那麼說明應用程式本身存在記憶體洩露,這個時候就需要我們對應用程式進行檢查,找出導緻記憶體洩露的地方,然後修正。

java.lang.OutOfMemory:PermGen space錯誤是由Perm space空間不足。一般出現這個錯誤是由加載了太多的類或者大量使用了動态代理造成的。如果出現了這個錯誤,我們可以将Perm空間調大一點。

-XX:PermSize=16M  -XX:MaxPermSize=64M

參考資料

1 http://developers.sun.com/mobility/midp/articles/garbage/

2 http://developers.sun.com/mobility/midp/articles/garbagecollection2/

3 http://blogs.sun.com/watt/resource/jvm-options-list.html

4 http://java.sun.com/developer/technicalArticles/Programming/turbo/

5 http://www.ibm.com/developerworks/library/j-jtp10283/index.html?S_TACT=105AGX52&S_CMP=cn-a-j

6 http://www.ibm.com/developerworks/library/j-jtp11253/index.html?S_TACT=105AGX52&S_CMP=cn-a-j

繼續閱讀