1. 程式人生 > >G1垃圾收集器和CMS垃圾收集器 (http://mm.fancymore.com/reading/G1-CMS%E5%9E%83%E5%9C%BE%E7%AE%97%E6%B3%95.html#toc_8)

G1垃圾收集器和CMS垃圾收集器 (http://mm.fancymore.com/reading/G1-CMS%E5%9E%83%E5%9C%BE%E7%AE%97%E6%B3%95.html#toc_8)

異常 了解 訪問 標識 2.6 響應時間 ads sco 方法

  • 參考來源
  • JVM 體系架構
    • 堆/棧的內存分配
    • 靜態和非靜態方法的內存分配
  • CMS 回收算法
    • 應用場景
    • CMS 垃圾收集階段劃分(Collection Phases)
    • CMS什麽時候啟動
    • CMS缺點
  • G1收集算法
    • G1的發展
    • 分代垃圾回收瓶頸
    • G1使用場景
    • G1特點
    • G1堆內存的分配
    • G1的進程內存占用(Footprint)
    • G1 收集器收集過程
    • G1命令行參數
    • 記錄G1的GC日誌
    • G1性能調優

參考來源

http://blog.csdn.net/renfufei/article/details/41897113

JVM 體系架構

https://github.com/cncounter/translation/raw/master/tiemao_2014/G1/01_1_JVM_Arch_CN.png

性能優化的關鍵部位:

  1. 堆(Heap)

  2. JIT編譯器, 新版本的JVM調優中很少需要關註.

  3. 垃圾收集器

性能基礎目標: 響應速度(responsiveness) 和/或 吞吐量(throughput)

堆/棧的內存分配

  Stack(棧)是JVM的內存指令區,順序分配,內存大小定長,速度很快;

  Heap(堆)是JVM的內存數據區,分配不定長的內存空間;

靜態和非靜態方法的內存分配

  • 非靜態方法有一個隱含的傳入參數,該參數是JVM給它的;

    這個隱含的參數就是對象實例在Stack中的地址指針。非靜態方法必須獲得該隱含參數,因此非靜態方法在調用前,必須先new一個對象實例,獲得Stack中的地址指針,否則JVM將無法將隱含參數傳給非靜態方法。

  • 靜態方法無此隱含參數,因此也不需要new對象;

    只要class文件被ClassLoader load進入JVM的Stack,該靜態方法即可被調用。當然此時靜態方法是存取不到Heap 中的對象屬性的。

  • 靜態屬性和動態屬性:

  前面提到對象實例以及動態屬性都是保存在Heap 中的,而Heap 必須通過Stack中的地址指針才能夠被指令(類的方法)訪問到。因此可以推斷出:靜態屬性是保存在Stack中的,而不同於動態屬性保存在Heap 中。正因為都是在Stack中,而Stack中指令和數據都是定長的,因此很容易算出偏移量,也因此不管什麽指令(類的方法),都可以訪問到類的靜態屬性。也正因為靜態屬性被保存在Stack中,所以具有了全局屬性。

  在JVM中,靜態屬性保存在Stack指令內存區,動態屬性保存在Heap數據內存區。

  • 方法加載過程:

當一個class文件被ClassLoader load進入JVM後,方法指令保存在Stack中,此時Heap 區沒有數據。然後程序技術器開始執行指令,

如果是靜態方法,直接依次執行指令代碼,當然此時指令代碼是不能訪問Heap 數據區的;

如果是非靜態方法,由於隱含參數沒有值,會報錯。因此在非靜態方法執行前,要先new對象,在Heap 中分配數據,並把Stack中的地址指針交給非靜態方法,這樣程序技術器依次執行指令,而指令代碼此時能夠訪問到Heap 數據區了。

CMS 回收算法

http://blog.csdn.net/wisgood/article/details/16368551

應用場景

CMS滿足對響應時間的重要性需求 大於對吞吐量的要求;

應用中存在比較多的長生命周期的對象的應用;

CMS用於年老代的回收,目標是盡量減少應用的暫停時間,減少full gc發生的幾率,利用和應用程序線程並發的垃圾回收線程來標記清除年老代。

CMS 垃圾收集階段劃分(Collection Phases)

CMS收集器在老年代堆內存的回收中執行分為以下階段:

  • (1). 初始標記 (Initial Mark)

    (Stop the World Event,所有應用線程暫停)

    從root對象開始標記存活的對象。

    暫停時間一般持續時間較短。

  • (2) 並發標記 (Concurrent Marking)

    和Java應用程序線程並發運行;

    遍歷老年代的對象圖,標記出活著的對象。

    掃描從被標記的對象開始,直到遍歷完從root可達的所有對象.

  • (3) 再次標記(Remark)

    (Stop the World Event, 所有應用線程暫停)

    查找在並發標記階段漏過的對象,這些對象是在並發收集器完成對象跟蹤之後由應用線程更新的.

  • (4) 並發清理(Concurrent Sweep)

    回收在標記階段(marking phases)確定為不可達的對象.

    垃圾對象占用的空間添加到一個空閑列表(free list),供以後的分配使用。死對象的合並可能在此時發生. 請註意,存活的對象並沒有被移動.

  • (5) 重置(Resetting) 清理數據結構,為下一個並發收集做準備.

CMS什麽時候啟動

與其他老年代的垃圾回收器相比,CMS在老年代空間占滿之前就應該開始。

CMS收集會在老年代的空閑時間少於某一個閾值的時候被觸發(這個閾值可以是動態統計出來的,也可以是固定設置的),而實際的回收周期可能要延遲到下一次年輕代的回收。為什麽要這樣,前面已經有解釋了。

在某些極端惡劣的情況下,對象會直接在老年代中進行分配,並且CMS回收周期開始的時候,eden區尚有非常多的對象。這個時候初始標記階段會有多於10-100倍的時間消耗。這個通常是因為要分配非常大的對象。幾兆的數組等。為了盡量避免長時間的暫停,我們需要合理的配置

-XX:CMSWaitDuration。

啟動CMS設置參數:

-XX:+UseConcMarkSweepGC

配置固定的CMS啟動閾值:

-XX:+UseCMSInitiatingOccupancyOnly
-XX:MCSInitiatingOccupancyFraction=70

顯示調用MCS周期

-XX:+ExlicitGCInvokesConcurrent

CMS的全量GC——FullGC

如果CMS不能夠在老年代清理出足夠的空間,會導致異常,使得JVM臨時啟動Serial Old垃圾回收方式進行回收。這個會造成長時間的stop-the-world暫停。全量的GC的原因可能有兩個:

CMS垃圾回收的速度跟不上了

老年代中有大量的內存碎片

當然,也有可能是,沒有為JVM分配足夠多的內存,從而導致OutofMemoryException。

永久代的回收

一個導致CMS需要進行全量GC的原因是永久代中的垃圾。默認情況下,CMS是不回收永久代中的垃圾的。如果在你的應用中使用了多個類加載器,或者反射機制,那麽就需要對永久代進行回收。采用參數-XX:+CMSClassUnloadingEnabled會打開永久代的垃圾回收。

利用多核:

通過使用以下的選項,可以使得CMS充分利用多核:

-XX:+CMSConcurrentMTEnabled  在並發階段,可以利用多核
-XX:+ConcGCThreads 指定線程數量
-XX:+ParallelGCThreads 指定在stop-the-world過程中,垃圾回收的線程數,默認是cpu的個數
-XX:+UseParNewGC 年輕代采用並行的垃圾回收器

CMS缺點

  1. CMS占用CPU資源,4個CPU以上才能更好發揮CMS優勢

    CMS並發階段,它不會導致用戶線程停頓,但會因為占用了一部分線程(或CPU資源)而導致應用程序變慢,總吞吐量會降低。

    CMS默認啟動的回收線程數是(CPU數量+3)/ 4,也就是當CPU在4個以上時,並發回收時垃圾收集線程最多占用不超過25%的CPU資源。但是當CPU不足4個時(譬如2個),那麽CMS對用戶程序的影響就可能變得很大,如果CPU負載本來就比較大的時候,還分出一半的運算能力去執行收集器線程,就可能導致用戶程序的執行速度忽然降低了50%,這也很讓人受不了。

    為了解決這種情況,虛擬機提供了一種稱為“增量式並發收集器”(Incremental Concurrent Mark Sweep / i-CMS)的CMS收集器變種,所做的事情和單CPU年代PC機操作系統使用搶占式來模擬多任務機制的思想一樣,就是在並發標記和並發清理的時候讓GC線程、用戶線程交替運行,盡量減少GC線程的獨占資源的時間,這樣整個垃圾收集的過程會更長,但對用戶程序的影響就會顯得少一些,速度下降也就沒有那麽明顯,但是目前版本中,i-CMS已經被聲明為“deprecated”,即不再提倡用戶使用。

  2. 產生浮動垃圾

    CMS收集器無法處理浮動垃圾(Floating Garbage),可能出現“Concurrent Mode Failure”失敗而導致另一次Full GC。

    原因:

    CMS並發清理階段,同時用戶線程還在運行著,伴隨程序的運行自然還會有新的垃圾不斷產生,這一部分垃圾出現在標記過程之後,CMS無法在本次收集中處理掉它們,只好留待下一次GC時再將其清理掉。

    這一部分垃圾就稱為“浮動垃圾”。也是由於在垃圾收集階段用戶線程還需要運行,即還需要預留足夠的內存空間給用戶線程使用,因此CMS收集器不能像其他收集器那樣等到老年代幾乎完全被填滿了再進行收集,需要預留一部分空間提供並發收集時的程序運作使用。

    在默認設置下,CMS收集器在老年代使用了68%的空間後就會被激活,這是一個偏保守的設置,如果在應用中老年代增長不是太快,可以適當調高參數-XX:CMSInitiatingOccupancyFraction的值來提高觸發百分比,以便降低內存回收次數以獲取更好的性能。要是CMS運行期間預留的內存無法滿足程序需要,就會出現一次“Concurrent Mode Failure”失敗,這時候虛擬機將啟動後備預案:臨時啟用Serial Old收集器來重新進行老年代的垃圾收集,這樣停頓時間就很長了。所以說參數-XX:CMSInitiatingOccupancyFraction設置得太高將會很容易導致大量“Concurrent Mode Failure”失敗,性能反而降低。

  3. 產生大量的空間碎片

    CMS是一款基於“標記-清除”算法實現的收集器,這意味著收集結束時會產生大量空間碎片。

    空間碎片過多時,將會給大對象分配帶來很大的麻煩,往往會出現老年代還有很大的空間剩余,但是無法找到足夠大的連續空間來分配當前對象,不得不提前觸發一次Full GC。

    為了解決這個問題,CMS收集器提供了一個-XX:+UseCMSCompactAtFullCollection開關參數,用於在“享受”完Full GC服務之後額外免費附送一個碎片整理過程,內存整理的過程是無法並發的。

    空間碎片問題沒有了,但停頓時間不得不變長了。虛擬機設計者們還提供了另外一個參數-XX: CMSFullGCsBeforeCompaction,這個參數用於設置在執行多少次不壓縮的Full GC後,跟著來一次帶壓縮的。

G1收集算法

G1的發展

  • 上一代的垃圾收集器(串行serial, 並行parallel, 以及CMS):

    都把堆內存劃分為固定大小的三個部分:

    年輕代(young generation), 
    
    年老代(old generation), 
    
    持久代(permanent generation).
    

    內存中的每個對象都存放在這三個區域中的一個.

  • G1 收集器采用一種邏輯上的劃分的方式來管理堆內存.

分代垃圾回收瓶頸

傳統分代垃圾回收方式的問題:Full GC所帶來的應用暫停

對實時性要求很高的應用場景下,GC暫停所帶來的請求堆積和請求失敗是無法接受的。這類應用可能要求請求的返回時間在幾百甚至幾十毫秒以內,如果分代垃圾回收方式要達到這個指標,只能把最大堆的設置限制在一個相對較小範圍內,但是這樣有限制了應用本身的處理能力,同樣也是不可接收的。

分代垃圾回收方式確實也考慮了實時性要求而提供了並發回收器,支持最大暫停時間的設置,但是受限於分代垃圾回收的內存劃分模型,其效果也不是很理想。

為了達到實時性的要求,一種新垃圾回收方式G1回收算法呼之欲出,它既支持短的暫停時間,又支持大的內存空間分配。可以很好的解決傳統分代方式帶來的問題。

G1使用場景

主要優勢:

停頓時間可控

實時性較強,大幅減少了長時間的gc

一定程度的高吞吐

推薦使用 G1 的場景(Recommended Use Cases)

G1的首要目標是為需要大量內存的系統提供一個保證GC低延遲的解決方案. 也就是說堆內存在6GB及以上,穩定和可預測的暫停時間小於0.5秒.

如果應用程序具有如下的一個或多個特征,那麽將垃圾收集器從CMS或ParallelOldGC切換到G1將會大大提升性能.

Full GC 次數太頻繁或者消耗時間太長

對象分配的頻率或代數提升(promotion)顯著變化

受夠了太長的垃圾回收或內存整理時間(超過0.5~1秒)

註意: 如果正在使用CMS或ParallelOldGC,而應用程序的垃圾收集停頓時間並不長,那麽繼續使用現在的垃圾收集器是個好主意. 使用最新的JDK時並不要求切換到G1收集器。

主要目標

從設計目標看G1完全是為了大型應用而準備的。

  • 支持很大的堆

  • 高吞吐量

    --支持多CPU和垃圾回收線程

    --在主線程暫停的情況下,使用並行收集

    --在主線程運行的情況下,使用並發收集

    實時目標:可配置在N毫秒內最多只占用M毫秒的時間進行垃圾回收

當然G1要達到實時性的要求,相對傳統的分代回收算法,在性能上會有一些損失。

G1特點

並行與並發:利用多CPU、多核縮短Stop-The-World停頓的時間;

分代收集:分代概念在G1中依然得以保留。

空間整合:整體基於“標記-整理”,局部(兩個Region之間)基於“復制”算法;不會產生空間碎片。

可預測的停頓:這是G1相對於CMS的另外一大優勢,能建立可預測的停頓時間模型,能讓使用者明確指定在一個長度為M毫秒的時間片段內,消耗在垃圾收集上的時間不得超過N毫秒。

G1堆內存的分配

  1. 堆內存被劃分為多個大小相等的 heap 區,每個heap區都是邏輯上連續的一段內存(virtual memory).

  2. 劃分的區域擁有角色,角色和老一代收集器相同的角色(eden, survivor, old);

  3. 每個角色的區域個數都不是固定的。這在內存使用上提供了更多的靈活性。

劃分區域:

1. Eden區 (年輕代)

2. Survivor區(年輕代)

3. Old區



4.(巨無霸區域)
5. (未分配區域)

還有第四種類型的對象被稱為巨無霸區域(Humongous regions):
這種巨無霸區是設計了用來保存比標準heap區大50%及以上的對象, 它們存儲在一組連續的區中.
最後一個類型是堆內存中的未使用區(unused areas).

分區大小:

每個heap區(Region)的大小在JVM啟動時就確定了. JVM 通常生成 2000 個左右的heap區, 根據堆內存的總大小,區的size範圍允許為 1Mb 到 32Mb.

存活的對象從一塊區域轉移(復制或移動)到另一塊區域。設計成 heap 區的目的是為了並行地進行垃圾回收(的同時停止/或不停止其他應用程序線程)

備註: 截止英文原文發表時,巨無霸對象的回收還沒有得到優化. 因此,您應該盡量避免創建太大(大於32MB?)的對象.

額外說明:

G1執行垃圾回收的處理方式與CMS相似. G1在全局標記段(gl階obal marking phase)並發執行, 以確定堆內存中哪些對象是存活的。標記階段完成後,G1就可以知道哪些heap區的empty空間最大;

它會首先回收這些區,通常會得到大量的自由空間. 這也是為什麽這種垃圾收集方法叫做Garbage-First(垃圾優先)的原因。顧名思義, G1將精力集中放在可能布滿可收回對象的區域, 可回收對象(reclaimable objects)也就是所謂的垃圾. G1使用暫停預測模型(pause prediction model)來達到用戶定義的目標暫停時間,並根據目標暫停時間來選擇此次進行垃圾回收的heap區域數量.

被G1標記為適合回收的heap區將使用轉移(evacuation)的方式進行垃圾回收. G1將一個或多個heap區域中的對象拷貝到其他的單個區域中,並在此過程中壓縮和釋放內存. 在多核CPU上轉移是並行執行的(parallel on multi-processors), 這樣能減少停頓時間並增加吞吐量. 因此,每次垃圾收集時, G1都會持續不斷地減少碎片, 並且在用戶給定的暫停時間內執行. 這比以前的方法強大了很多. CMS垃圾收集器(Concurrent Mark Sweep,並發標記清理)不進行壓縮. ParallelOld 垃圾收集只對整個堆執行壓縮,從而導致相當長的暫停時間。

需要強調的是, G1並不是一款實時垃圾收集器(real-time collector). 能以極高的概率在設定的目標暫停時間內完成,但不保證絕對在這個時間內完成。 基於以前收集的各種監控數據, G1會根據用戶指定的目標時間來預估能回收多少個heap區. 因此,收集器有一個相當精確的heap區耗時計算模型,並根據該模型來確定在給定時間內去回收哪些heap區.

註意 G1分為兩個階段: 並發階段(concurrent, 與應用線程一起運行, 如: 細化 refinement、標記 marking、清理 cleanup) 和 並行階段(parallel, 多線程執行, 如: 停止所有JVM線程, stop the world). 而 FullGC(完整垃圾收集)仍然是單線程的, 但如果進行適當的調優,則應用程序應該能夠避免 full GC。

G1的進程內存占用(Footprint)

如果從 ParallelOldGC 或者 CMS收集器遷移到 G1, 您可能會看到JVM進程占用更多的內存(a larger JVM process size). 這在很大程度上與 “accounting” 數據結構有關, 如 Remembered Sets 和 Collection Sets.

  • Remembered Sets 簡稱 RSets:

    跟蹤指向某個heap區內的對象引用. 堆內存中的每個區都有一個 RSet. RSet 使heap區能並行獨立地進行垃圾集合. RSets的總體影響小於5%.

  • Collection Sets 簡稱 CSets:

    收集集合, 在一次GC中將執行垃圾回收的heap區. GC時在CSet中的所有存活數據(live data)都會被轉移(復制/移動). 集合中的heap區可以是 Eden, survivor, 和/或 old generation. CSets所占用的JVM內存小於1%.

G1 收集器收集過程

首先得了解上述的G1內存分配模型。(年輕代 + 老年代);

  1. 年輕代上的GC

    存活的對象被轉移(copied or moved)到一個/或多個存活區(survivor regions). 如果存活時間達到閥值,這部分對象就會被提升到老年代(promoted to old generation regions).

    此時會有一次 stop the world(STW)暫停. 會計算出 Eden大小和 survivor 大小,給下一次年輕代GC使用. 清單統計信息 (Accounting)保存了用來輔助計算size. 諸如暫停時間目標之類的東西也會納入考慮.

    這種方法使得調整各代區域的尺寸很容易, 讓其更大或更小一些以滿足需要.

    總結起來,G1的年輕代收集歸納如下:

    • 堆一整塊內存空間,被分為多個heap區(regions).
    • 年輕代內存由一組不連續的heap區組成. 這使得在需要時很容易進行容量調整.
    • 年輕代的垃圾收集,或者叫 young GCs, 會有 stop the world 事件. 在操作時所有的應用程序線程都會被暫停(stopped).
    • 年輕代 GC 通過多線程並行進行.
    • 存活的對象被拷貝到新的 survivor 區或者老年代.

  2. G1老年代的GC

    和 CMS 收集器相似, G1 收集器也被設計為用來對老年代的對象進行低延遲(low pause)的垃圾收集. 下表描述了G1收集器在老年代進行垃圾回收的各個階段.

    G1 收集器在老年代堆內存中執行下面的這些階段. 註意有些階段也是年輕代垃圾收集的一部分:

    1. 初始標記(Initial Mark)

      也是年輕代收集的一部分

      (Stop the World Event,所有應用線程暫停) 此時會有一次 stop the world(STW)暫停事件;

      在G1中, 這附加在(piggybacked on)一次正常的年輕代GC. 標記可能有引用指向老年代對象的survivor區(根regions).

    2. 掃描根區域(Root Region Scanning)

      掃描初始標記的對象的可達性(掃描 survivor 區中引用到老年代的引用) 這個階段應用程序的線程會繼續運行. 在年輕代GC可能發生之前此階段必須完成.

    3. 並發標記(Concurrent Marking)

      在整個堆中查找活著的對象. 此階段應用程序的線程正在運行. 此階段可以被年輕代GC打斷(interrupted).

    4. 再次標記(Remark)

      (Stop the World Event,所有應用線程暫停) 完成堆內存中存活對象的標記. 使用一個叫做 snapshot-at-the-beginning(SATB, 起始快照)的算法, 該算法比CMS所使用的算法要快速的多.

    5. 清理(Cleanup) (Stop the World Event,所有應用線程暫停,並發執行)

      在存活對象和完全空閑的區域上執行統計(accounting). (Stop the world) 擦寫 Remembered Sets. (Stop the world)

      重置空heap區並將他們返還給空閑列表(free list). (Concurrent, 並發) (*) 拷貝(Copying) (Stop the World Eenvt,所有應用線程暫停) 產生STW事件來轉移或拷貝存活的對象到新的未使用的 heap區(new unused regions). 只在年輕代發生時日誌會記錄為 [GC pause (young)]. 如果在年輕代和老年代一起執行 則會被日誌記錄為 [GC Pause (mixed)].

    G1對老年代的GC有如下幾個關鍵點:

    • 並發標記清理階段(Concurrent Marking Phase)

      活躍度信息在程序運行的時候被並行計算出來

      活躍度(liveness)信息標識出哪些區域在轉移暫停期間最適合回收.

      不像CMS一樣有清理階段(sweeping phase).

    • 再次標記階段(Remark Phase)

      使用的 Snapshot-at-the-Beginning (SATB, 開始快照) 算法比起 CMS所用的算法要快得多.

      完全空的區域直接被回收.

    • 拷貝/清理階段(Copying/Cleanup Phase)

      年輕代與老年代同時進行回收.

      老年代的選擇基於其活躍度(liveness).

G1命令行參數

下面是啟動 Java2Demo示例程序的命令行示例. Java2Demo位於下載 JDK demos and samples 後解壓的文件夾中:

java -Xmx50m -Xms50m -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -jar c:\javademos\demo\jfc\Java2D\Java2demo.jar

關鍵命令行開關

-XX:+UseG1GC 讓 JVM 使用 G1 垃圾收集器.

-XX:MaxGCPauseMillis=200 設置最大GC停頓時間(GC pause time)指標(target). 這是一個軟性指標(soft goal), JVM 會盡力去達成這個目標. 所以有時候這個目標並不能達成. 默認值為 200 毫秒.

-XX:InitiatingHeapOccupancyPercent=45 啟動並發GC時的堆內存占用百分比. G1用它來觸發並發GC周期,基於整個堆的使用率,而不只是某一代內存的使用比例。值為 0 則表示“一直執行GC循環)‘. 默認值為 45 (例如, 全部的 45% 或者使用了45%).

最佳G1實踐建議:

在使用 G1 作為垃圾收集器時,你應該遵循下面這些最佳實踐的指導.

  • 不要設置年輕代的大小(Young Generation Size)

    假若通過 -Xmn 顯式地指定了年輕代的大小, 則會幹擾到 G1收集器的默認行為.

    G1在垃圾收集時將不再關心暫停時間指標. 所以從本質上說,設置年輕代的大小將禁用暫停時間目標.

    G1在必要時也不能夠增加或者縮小年輕代的空間. 因為大小是固定的,所以對更改大小無能為力.

  • 響應時間指標(Response Time Metrics)

    設置 XX:MaxGCPauseMillis=<N> 時不應該使用平均響應時間(ART, average response time) 作為指標,而應該考慮使用目標時間的90%或者更大作為響應時間指標. 也就是說90%的用戶(客戶端/?)請求響應時間不會超過預設的目標值. 記住,暫停時間只是一個目標,並不能保證總是得到滿足.

什麽是轉移失敗(Evacuation Failure)?

對 survivors 或 promoted objects 進行GC時如果JVM的heap區不足就會發生提升失敗(promotion failure). 堆內存不能繼續擴充,因為已經達到最大值了. 當使用 -XX:+PrintGCDetails 時將會在GC日誌中顯示 to-space overflow (to-空間溢出)。

這是很昂貴的操作!

  • GC仍繼續所以空間必須被釋放.
  • 拷貝失敗的對象必須被放到正確的位置(tenured in place).
  • CSet指向區域中的任何 RSets 更新都必須重新生成(regenerated).

所有這些步驟都是代價高昂的.

如何避免轉移失敗(Evacuation Failure)?

要避免避免轉移失敗, 考慮采納下列選項.

  1. 增加堆內存大小

  2. 增加 -XX:G1ReservePercent=n, 其默認值是 10.

    G1創建了一個假天花板(false ceiling),在需要更大 ‘to-space’ 的情況下會嘗試從保留內存獲取(leave the reserve memory free).

  3. 更早啟動標記周期(marking cycle)

    通過采用 -XX:ConcGCThreads=n 選項增加標記線程(marking threads)的數量.

G1 的 GC 參數完全列表

-XX:+UseG1GC    使用 G1 (Garbage First) 垃圾收集器
-XX:MaxGCPauseMillis=n  設置最大GC停頓時間(GC pause time)指標(target). 這是一個軟性指標(soft goal), JVM 會盡量去達成這個目標.
-XX:InitiatingHeapOccupancyPercent=n    啟動並發GC周期時的堆內存占用百分比. G1之類的垃圾收集器用它來觸發並發GC周期,基於整個堆的使用率,而不只是某一代內存的使用比. 值為 0 則表示"一直執行GC循環". 默認值為 45.
-XX:NewRatio=n  新生代與老生代(new/old generation)的大小比例(Ratio). 默認值為 2.
-XX:SurvivorRatio=n eden/survivor 空間大小的比例(Ratio). 默認值為 8.
-XX:MaxTenuringThreshold=n  提升年老代的最大臨界值(tenuring threshold). 默認值為 15.
-XX:ParallelGCThreads=n 設置垃圾收集器在並行階段使用的線程數,默認值隨JVM運行的平臺不同而不同.
-XX:ConcGCThreads=n 並發垃圾收集器使用的線程數量. 默認值隨JVM運行的平臺不同而不同.
-XX:G1ReservePercent=n  設置堆內存保留為假天花板的總量,以降低提升失敗的可能性. 默認值是 10.
-XX:G1HeapRegionSize=n  使用G1時Java堆會被分為大小統一的的區(region)。此參數可以指定每個heap區的大小. 默認值將根據 heap size 算出最優解. 最小值為 1Mb, 最大值為 32Mb.

上面是完整的 G1 的 GC 開關參數列表. 在使用時請記住上面所述的最佳實踐.

記錄G1的GC日誌

我們要介紹的最後一個主題是使用日誌信息來分享G1收集器的性能. 本節簡要介紹垃圾收集的相關參數,以及日誌中打印的相關信息. 設置日誌細節(Log Detail)

可以設置3種不同的日誌級別.

(1) -verbosegc (等價於 -XX:+PrintGC) 設置日誌級別為 好 fine.

日誌輸出示例

[GC pause (G1 Humongous Allocation) (young) (initial-mark) 24M- >21M(64M), 0.2349730 secs]
[GC pause (G1 Evacuation Pause) (mixed) 66M->21M(236M), 0.1625268 secs]    

(2) -XX:+PrintGCDetails 設置日誌級別為 更好 finer. 使用此選項會顯示以下信息:

每個階段的 Average, Min, 以及 Max 時間.
根掃描(Root Scan), RSet 更新(同時處理緩沖區信息), RSet掃描(Scan), 對象拷貝(Object Copy), 終止(Termination, 包括嘗試次數).
還顯示 “other” 執行時間, 比如選擇 CSet, 引用處理(reference processing), 引用排隊(reference enqueuing) 以及釋放(freeing) CSet等.
顯示 Eden, Survivors 以及總的 Heap 占用信息(occupancies).

日誌輸出示例

[Ext Root Scanning (ms): Avg: 1.7 Min: 0.0 Max: 3.7 Diff: 3.7]
[Eden: 818M(818M)->0B(714M) Survivors: 0B->104M Heap: 836M(4096M)->409M(4096M)]

(3) -XX:+UnlockExperimentalVMOptions -XX:G1LogLevel=finest 設置日誌級別為 最好 finest. 和 finer 級別類似, 包含每個 worker 線程信息.

   [Ext Root Scanning (ms): 2.1 2.4 2.0 0.0
       Avg: 1.6 Min: 0.0 Max: 2.4 Diff: 2.3]
   [Update RS (ms):  0.4  0.2  0.4  0.0
       Avg: 0.2 Min: 0.0 Max: 0.4 Diff: 0.4]
       [Processed Buffers : 5 1 10 0
       Sum: 16, Avg: 4, Min: 0, Max: 10, Diff: 10]

Determining Time:

有兩個參數決定了GC日誌中打印的時間顯示形式.

(1) -XX:+PrintGCTimeStamps - 顯示從JVM啟動時算起的運行時間.

日誌輸出示例

1.729: [GC pause (young) 46M->35M(1332M), 0.0310029 secs]

(2) -XX:+PrintGCDateStamps - 在每條記錄前加上日期時間.

日誌輸出示例

2012-05-02T11:16:32.057+0200: [GC pause (young) 46M->35M(1332M), 0.0317225 secs]

理解 G1 日誌:

為了使你更好地理解GC日誌, 本節通過實際的日誌輸出,定義了許多專業術語. 下面的例子顯示了GC日誌的內容,並加上日誌中出現的術語和值的解釋說明.

Note: 更多信息請參考 Poonam Bajaj的博客: G1垃圾回收日誌. G1 日誌相關術語

Clear CT
CSet
External Root Scanning
Free CSet
GC Worker End
GC Worker Other
Object Copy
Other
Parallel Time
Ref Eng
Ref Proc
Scanning Remembered Sets
Termination Time
Update Remembered Set
Worker Start

Parallel Time(並行階段耗時):

414.557: [GC pause (young), 0.03039600 secs] [Parallel Time: 22.9 ms]
[GC Worker Start (ms): 7096.0 7096.0 7096.1 7096.1 706.1 7096.1 7096.1 7096.1 7096.2 7096.2 7096.2 7096.2
   Avg: 7096.1, Min: 7096.0, Max: 7096.2, Diff: 0.2]

Parallel Time – 主要並行部分運行停頓的整體時間

Worker Start – 各個工作線程(workers)啟動時的時間戳(Timestamp)

Note: 日誌是根據 thread id 排序,並且每條記錄都是一致的. External Root Scanning(外部根掃描)

[Ext Root Scanning (ms): 3.1 3.4 3.4 3.0 4.2 2.0 3.6 3.2 3.4 7.7 3.7 4.4
 Avg: 3.8, Min: 2.0, Max: 7.7, Diff: 5.7]

External root scanning - 掃描外部根花費的時間(如指向堆內存的系統詞典(system dictionary)等部分) Update Remembered Set(更新 RSet)

[Update RS (ms): 0.1 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 Avg: 0.0, Min: 0.0, Max: 0.1, Diff: 0.1]
[Processed Buffers : 26 0 0 0 0 0 0 0 0 0 0 0
Sum: 26, Avg: 2, Min: 0, Max: 26, Diff: 26]

Update Remembered Set - 必須更新在pause之前已經完成但尚未處理的緩沖. 花費的時間取決於cards的密度。cards越多,耗費的時間就越長。 Scanning Remembered Sets(掃描 RSets)

[Scan RS (ms): 0.4 0.2 0.1 0.3 0.0 0.0 0.1 0.2 0.0 0.1 0.0 0.0 Avg: 0.1, Min: 0.0, Max: 0.4, Diff: 0.3]F

Scanning Remembered Sets - 查找指向 Collection Set 的指針(pointers) Object Copy(對象拷貝)

[Object Copy (ms): 16.7 16.7 16.7 16.9 16.0 18.1 16.5 16.8 16.7 12.3 16.4 15.7 Avg: 16.3, Min: 12.3, Max: 18.1, Diff: 5.8]

Object copy – 每個獨立的線程在拷貝和轉移對象時所消耗的時間. Termination Time(結束時間)

[Termination (ms): 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 Avg: 0.0, Min: 0.0, Max: 0.0, Diff: 0.0] [Termination Attempts : 1 1 1 1 1 1 1 1 1 1 1 1 Sum: 12, Avg: 1, Min: 1, Max: 1, Diff: 0]

Termination time - 當worker線程完成了自己那部分對象的復制和掃描,就進入終止協議(termination protocol)。它查找未完成的工作(looks for work to steal), 一旦它完成就會再進入終止協議。 終止嘗試記錄(Termination attempt counts)所有查找工作的嘗試次數(attempts to steal work). GC Worker End

[GC Worker End (ms): 7116.4 7116.3 7116.4 7116.3 7116.4 7116.3 7116.4 7116.4 7116.4 7116.4 7116.3 7116.3 Avg: 7116.4, Min: 7116.3, Max: 7116.4, Diff: 0.1] [GC Worker (ms): 20.4 20.3 20.3 20.2 20.3 20.2 20.2 20.2 20.3 20.2 20.1 20.1 Avg: 20.2, Min: 20.1, Max: 20.4, Diff: 0.3]

GC worker end time – 獨立的 GC worker 停止時的時間戳.

GC worker time – 每個獨立的 GC worker 線程消耗的時間. GC Worker Other

[GC Worker Other (ms): 2.6 2.6 2.7 2.7 2.7 2.7 2.7 2.8 2.8 2.8 2.8 2.8
Avg: 2.7, Min: 2.6, Max: 2.8, Diff: 0.2]

GC worker other – 每個GC線程中不能歸屬到之前列出的worker階段的其他時間. 這個值應該很低. 過去我們見過很高的值,是由於JVM的其他部分的瓶頸引起的(例如在分層[Tiered]代碼緩存[Code Cache]占有率的增加)。 Clear CT

[Clear CT: 0.6 ms]

清除 RSet 掃描元數據(scanning meta-data)的 card table 消耗的時間. Other

[Other: 6.8 ms]

其他各種GC暫停的連續階段花費的時間. CSet

[Choose CSet: 0.1 ms]

敲定要進行垃圾回收的region集合時消耗的時間. 通常很小,在必須選擇 old 區時會稍微長一點點. Ref Proc

[Ref Proc: 4.4 ms]

處理 soft, weak, 等引用所花費的時間,不同於前面的GC階段 Ref Enq

[Ref Enq: 0.1 ms]

將 soft, weak, 等引用放置到待處理列表(pending list)花費的時間. Free CSet

[Free CSet: 2.0 ms]

釋放剛被垃圾收集的 heap區所消耗的時間,包括對應的remembered sets。

G1性能調優

G1性能調優實踐Spark的應用: http://dataunion.org/19227.html

G1性能的分析:http://blog.csdn.net/woshiqjs/article/details/7290513

G1垃圾收集器和CMS垃圾收集器 (http://mm.fancymore.com/reading/G1-CMS%E5%9E%83%E5%9C%BE%E7%AE%97%E6%B3%95.html#toc_8)