參考來源

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