NO IMAGE
目錄

G1垃圾收集器入門

說明

concurrent: 併發, 多個執行緒協同做同一件事情(有狀態)

parallel: 並行, 多個執行緒各做各的事情(互相間無共享狀態)

參考: What’s the difference between concurrency and parallelism

概述

目的

本文介紹如何使用G1,及在 Hotspot JVM 中怎麼使用G1垃圾收集器。 您將瞭解 G1 收集器的內部原理, 切換為 G1 收集器的命令列引數, 以及讓其記錄GC日誌的選項。

需要的時間

大約 1 個小時

簡介

本文涵蓋了Java虛擬機器(JVM, Java Virtual Machine)中 G1 的基礎知識。

  1. 第一部分, 簡單概述JVM的同時介紹了垃圾收集和效能.
  2. 接下來講述了 Hotspot JVM 中 CMS 收集器是如何工作的.
  3. 接著再一步一步地指導在 Hotspot JVM 中使用G1進行垃圾回收的工作方式.
  4. 之後的一個小節介紹 G1 垃圾收集器可用的命令列引數.
  5. 最後,您將瞭解如何配置使G1收集器記錄日誌.

硬體與軟體環境需求

下面是 硬體與軟體環境需求 清單:

  • 一臺PC機, 執行 Windows XP 以上作業系統, Mac OS X 或者 Linux 都可以. 注意,因為作者在Windows 7上進行開發和測試, 尚未在所有平臺上完成測試。 但在 OS X和Linux 上應該也是正常的。最好配置了多核CPU.
  • Java 7 Update 9 或更高版本
  • 最新的 Java 7 Demos and Samples Zip 檔案

準備條件

在開始學習本教程之前, 你需要:

  • 下載並安裝最新的 Java JDK (JDK 7 u9 或 以後的版本): Java 7 JDK 下載頁面

  • 下載並安裝 Demos and Samples (示例與樣例) zip 檔案, 下載頁面和JDK相同. 然後解壓到合適的位置. 如:C:\javademos

Java 技術 和 JVM

Java 概述

Java 是 Sun Microsystems 公司在1995年釋出的一門程式語言. 同時也是一個執行Java程式的底層平臺. 提供工具、遊戲和企業應用程式支援。Java 執行在全世界超過8.5億的PC,以及數十億的智慧裝置上,包括 mobile 和 TV. Java 是由許多關鍵部件組成的一個整體, 統稱為Java平臺。

JRE(Java Runtime Edition)

一般來說下載了Java以後, 你就得到了一個Java執行時: Java Runtime Environment (JRE). JRE 由Java虛擬機器 Java Virtual Machine (JVM), Java 平臺核心類(core classes), 以及 Java平臺支援庫組成. 必須有這三大元件的支援才能在你的電腦上執行 Java 程式. 例如 Java 7, 可以在作業系統上作為桌面應用程式執行, 還可以通過 Java Web Start 從Web上安裝, 或者是作為嵌入式Web程式在瀏覽器中執行 (通過 JavaFX).

Java 程式語言

Java 是一門物件導向程式語言(object-oriented programming language), 包涵以下特性.

  • Platform Independence – Java 應用程式被編譯為位元組碼(bytecode)存放到 class 檔案中, 由JVM載入. 因為程式在 JVM 中執行, 所以可以跨平臺執行在各種作業系統/裝置上.
  • Object-Oriented – Java 是一門物件導向的語言, 繼承了 C 和 C 的很多特性,並在此基礎上進行擴充和優化.
  • Automatic Garbage Collection – Java對記憶體進行 自動分配(allocates) 和自動釋放(deallocates). 所以程式不再執行這一繁瑣的任務(其實自動記憶體回收,更多的好處是減少了程式設計需要重複處理的這種細節,另一個例子是對JDBC的封裝).
  • Rich Standard Library – Java包含大量的標準物件,可以執行諸如輸入輸出(input/output), 網路操作以及日期處理等任務.

JDK(Java Development Kit)

JDK 是用來開發Java程式的一系列工具集. 通過JDK, 你可以編譯用Java語言書寫的程式, 並在 JVM 中執行. 另外, JDK 還提供了打包(packaging)和分發(distributing)程式的工具.

JDK 和 JRE 使用同樣的 Java Application Programming Interfaces (Java API).Java API 是預先打包好以供程式設計師用來開發程式的類庫集合. 通過 Java API 使得很多常規任務可以很輕鬆的就完成,如 字串操作(string manipulation), 時間日期處理(date/time processing), 網路程式設計(networking), 以及實現各種資料結構(data structures, 如 lists, maps, stacks, and queues).

JVM(Java Virtual Machine)

Java Virtual Machine (JVM) 是一臺抽象的計算機(abstract computing machine). JVM 本質是一個程式, 但在執行於JVM上的程式看來, 他就像一臺真實機器一樣. 這樣, Java程式就能使用相同的介面和庫. 每種特定作業系統上的 JVM 實現, 都將 Java 程式指令轉換為本地機器的指令(instructions)和命令(commands). 由此,實現了Java程式的平臺獨立性.

Java虛擬機器的第一個原型實現,由 Sun Microsystems, Inc. 完成, 在一臺手持裝置上用軟體模擬了 Java虛擬機器指令集, 類似於今天的 PDA(Personal Digital Assistant). Oracle 當前在移動裝置,桌面系統和伺服器上都提供了Java虛擬機器實現, 但Java虛擬機器不限制使用任何特定的技術,硬體,或作業系統。JVM也不一定都是基於軟體的,你可以直接在硬體CPU上實現JVM指令, 還可以晶片上實現,或者採用 microcode 的方式來實現.

Java 虛擬機器完全不關心Java語言的細節, 只識別 class 檔案這種特定的二進位制格式. 一個 class 檔案包含 Java虛擬機器指令(或稱之為位元組碼 bytecode) 及符號變數表(symbol table), 還有一些輔助資訊.

基於安全性考慮, Java虛擬機器對 class 檔案中的程式碼執行 強語法檢查和組成結構規範限制. 既然虛擬機器有這種特徵, 那麼任何一門程式語言,只要能編譯為合法的 class 檔案,都可以載入到 Java虛擬機器 裡面執行。由於具有通用性,跨平臺特性, 其他語言的實現者可以把Java虛擬機器作為該語言的載入執行工具。(1) The Java Virtual Machine

探索 JVM 體系架構

Hotspot 架構

HotSpot JVM 有一個穩定強悍的架構, 支援強大的功能與特性, 具備實現高效能和大規模可伸縮性的能力。例如,HotSpot JVM JIT編譯器能動態進行優化生成。換句話說,他們執行Java程式時,會針對底層系統架構動態生成高效能的本地機器指令。此外,通過成熟的演進和執行時環境的持續工程,加上多執行緒垃圾收集器,HotSpot JVM即使實在大型計算機系統上也能獲得很高的伸縮性.

HotSpot JVM: Architecture

JVM 的主要元件包括: 類載入器(class loader), 執行時資料區(runtime data areas), 以及執行引擎(execution engine).

Hotspot 關鍵部分

與效能(performance)有關的部分是 JVM 最重要的元件,下圖中用高亮的顏色來顯示.

對JVM進行效能調優時有三大元件需要重點關注。堆(Heap)是存放物件的記憶體空間。這個區域由JVM啟動時選擇的垃圾收集器進行管理。大多數調優引數都是調整堆記憶體的大小,以及根據實際情況選擇最合適的垃圾收集器. JIT編譯器也對效能有很大的影響, 但新版本的JVM調優中很少需要關注.

效能基礎

大多數情況下對 Java 程式進行調優, 主要關注兩個目標之一: 響應速度(responsiveness) 和/或 吞吐量(throughput). 下面的教程中我們將講述這些概念.

響應能力(Responsiveness)

響應能力就是程式或系統對一個請求的響應有多迅速. 比如:

  • 程式UI響應速度有多靈敏
  • 網站頁面響應有多快
  • 資料庫查詢有多快

對響應速度要求很高的系統, 較大的停頓時間(large pause times) 是不可接受的. 重點是在非常短的時間週期內快速響應.

吞吐量(Throughput)

吞吐量關注在一個特定時間段內應用系統的最大工作量。衡量吞吐量的指標/示例包括:

  • 給定時間內完成的事務數.
  • 每小時批處理系統能完成的作業(jobs)數量.
  • 每小時能完成多少次資料庫查詢

在吞吐量方面優化的系統, 停頓時間長(High pause times)也是可以接受的。由於高吞吐量應用執行時間長,所以此時更關心的是如何儘可能快地完成整個任務,而不考慮快速響應。

G1 垃圾收集器(Garbage Collector)

G1 垃圾收集器

G1 (Garbage-First)是一款面向伺服器的垃圾收集器,主要針對配備多顆處理器及大容量記憶體的機器. 以極高概率滿足GC停頓時間要求的同時,還具備高吞吐量效能特徵. 在Oracle JDK 7 update 4 及以上版本中得到完全支援, 專為以下應用程式設計:

  • 可以像CMS收集器一樣,GC操作與應用的執行緒一起併發執行
  • 緊湊的空閒記憶體區間且沒有很長的GC停頓時間.
  • 需要可預測的GC暫停耗時.
  • 不想犧牲太多吞吐量效能.
  • 啟動後不需要請求更大的Java堆.

G1的長期目標是取代CMS(Concurrent Mark-Sweep Collector, 併發標記-清除). 因為特性的不同使G1成為比CMS更好的解決方案. 一個區別是,G1是一款壓縮型的收集器.G1通過有效的壓縮完全避免了對細微空閒記憶體空間的分配,不用依賴於regions,這不僅大大簡化了收集器,而且還消除了潛在的記憶體碎片問題。除壓縮以外,G1的垃圾收集停頓也比CMS容易估計,也允許使用者自定義所希望的停頓引數(pause targets)

G1 操作概述

上一代的垃圾收集器(序列serial, 並行parallel, 以及CMS)都把堆記憶體劃分為固定大小的三個部分: 年輕代(young generation), 年老代(old generation), 以及持久代(permanent generation).

記憶體中的每個物件都存放在這三個區域中的一個.

而 G1 收集器採用一種不同的方式來管理堆記憶體.

堆記憶體被劃分為多個大小相等的 heap 區,每個heap區都是邏輯上連續的一段記憶體(virtual memory). 其中一部分割槽域被當成老一代收集器相同的角色(eden, survivor, old), 但每個角色的區域個數都不是固定的。這在記憶體使用上提供了更多的靈活性。

G1執行垃圾回收的處理方式與CMS相似. G1在全域性標記階段(global 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 的場景(Recommended Use Cases)

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

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

  • Full GC 次數太頻繁或者消耗時間太長.
  • 物件分配的頻率或代數提升(promotion)顯著變化.
  • 受夠了太長的垃圾回收或記憶體整理時間(超過0.5~1秒)

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

CMS的GC概述

分代GC(Generational GC)與 CMS

併發標記清理(CMS, Concurrent Mark Sweep)收集器(也稱為多併發低暫停的收集器)回收老年代記憶體(tenured generation). 它將垃圾回收中的絕大部分工作與應用程式的執行緒一起併發執行,以期能最小化暫停時間. 通常多併發低暫停收集器收集器不復制或也不壓縮存活的物件. 垃圾回收不移動存活的物件, 如果產生記憶體碎片問題,就會分配/佔用更大的堆記憶體空間。

注意: 年輕代使用的CMS收集器也和並行收集器採用一樣的演算法.

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

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

階段說明
(1) 初始標記 (Initial Mark)(Stop the World Event,所有應用執行緒暫停) 在老年代(old generation)中的物件, 如果從年輕代(young generation)中能訪問到, 則被 “標記,marked” 為可達的(reachable).物件在舊一代“標誌”可以包括這些物件可能可以從年輕一代。暫停時間一般持續時間較短,相對小的收集暫停時間.
(2) 併發標記 (Concurrent Marking)在Java應用程式執行緒執行的同時遍歷老年代(tenured generation)的可達物件圖。掃描從被標記的物件開始,直到遍歷完從root可達的所有物件. 調整器(mutators)在併發階段的2、3、5階段執行,在這些階段中新分配的所有物件(包括被提升的物件)都立刻標記為存活狀態.
(3) 再次標記(Remark)(Stop the World Event, 所有應用執行緒暫停) 查詢在併發標記階段漏過的物件,這些物件是在併發收集器完成物件跟蹤之後由應用執行緒更新的.
(4) 併發清理(Concurrent Sweep)回收在標記階段(marking phases)確定為不可及的物件. 死物件的回收將此物件佔用的空間增加到一個空閒列表(free list),供以後的分配使用。死物件的合併可能在此時發生. 請注意,存活的物件並沒有被移動.
(5) 重置(Resetting)清理資料結構,為下一個併發收集做準備.

CMS的GC步驟

接下來,讓我們一步步地講述CMS收集器的操作.

1. CMS的堆記憶體結構(Heap Structure)

堆記憶體被分為3個空間.

年輕代(Young generation)分為 1個新生代空間(Eden)和2個存活區(survivor spaces). 老年代(Old generation)是一大塊連續的空間, 垃圾回收(Object collection)就地解決(is done in place), 除了 Full GC, 否則不會進行壓縮(compaction).

2. CMS年輕代(Young) GC 的工作方式

年輕代(young generation)用高亮的綠色表示, 老年代(old generation)用藍色表示。如果程式執行了一段時間,那麼 CMS 看起來就像下圖這個樣子. 物件散落在老年代中的各處地方.

在使用 CMS 時, 老年代的物件回收就地進行(deallocated in place). 他們不會被移動到其他地方. 除了 Full GC, 否則記憶體空間不會進行壓縮.

3. 年輕代垃圾回收(Young Generation Collection)

Eden區和survivor區中的存活物件被拷貝到另一個空的survivor 區. 存活時間更長,達到閥值的物件會被提升到老年代(promoted to old generation).

4. 年輕代(Young) GC 之後

年輕代(Young)進行一次垃圾回收之後, Eden 區被清理乾淨(cleared),兩個 survivor 區中的一個也被清理乾淨了. 如下圖所示:

圖中新提升的物件用深藍色來標識. 綠色的部分是年輕代中存活的物件,但還沒被提升到老年代中.

5. CMS的老年代回收(Old Generation Collection)

兩次stop the world事件發生在: 初始標記(initial mark)以及重新標記(remark)階段. 當老年代達到一定的佔有率時,CMS垃圾回收器就開始工作.

(1) 初始標記(Initial mark)階段的停頓時間很短,在此階段存活的(live,reachable,可及的) 物件被記下來. (2) 併發標記(Concurrent marking)在程式繼續執行的同時找出存活的物件. 最後, 在第(3)階段(remark phase), 查詢在第(2)階段(concurrent marking)中錯過的物件.

6. 老年代回收 – 併發清理(Concurrent Sweep)

在前面階段未被標記的物件將會就地釋放(deallocated in place). 此處沒有壓縮(compaction).

備註: 未標記(Unmarked)的物件 == 已死物件(Dead Objects)

7. 老年代回收 – 清理之後(After Sweeping)

在第(4)步(Sweeping phase)之後, 可以看到很多記憶體被釋放了. 還應該注意到,這裡並沒有執行記憶體壓縮整理(no compaction).

最後, CMS 收集器進入(move through)第(5)階段, 重置(resetting phase), 然後等候下一次的GC閥值到來(GC threshold).

G1垃圾收集器概述

一步步介紹G1

G1收集器採用一種不同的方式來分配堆. 下面通過圖解的方式一步步地講述G1系統.

1. G1的堆記憶體結構

堆記憶體被劃分為固定大小的多個區域.

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

2. G1 堆空間分配

實際上,這些區域(regions)被對映為邏輯上的 Eden, Survivor, 和 old generation(老年代)空間.

圖中的顏色標識了每一個區域屬於哪個角色. 存活的物件從一塊區域轉移(複製或移動)到另一塊區域。設計成 heap 區的目的是為了並行地進行垃圾回收(的同時停止/或不停止其他應用程式執行緒).

如圖所示,heap區可以分配為 Eden, Survivor, 或 old generation(老年代)區. 此外,還有第四種型別的物件被稱為巨無霸區域(Humongous regions),這種巨無霸區是設計了用來儲存比標準塊(standard region)大50%及以上的物件, 它們儲存在一組連續的區中. 最後一個型別是堆記憶體中的未使用區(unused areas).

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

3. G1中的年輕代(Young Generation)

堆被分為大約2000個區. 最小size為1 Mb, 最大size為 32Mb. 藍色的區儲存老年代物件,綠色區域儲存年輕代物件.

注意G1中各代的heap區不像老一代垃圾收集器一樣要求各部分是連續的.

4. G1中的一次年輕代GC

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

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

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

5. G1的一次年輕代GC完成後

存活物件被轉移到存活區(survivor regions) 或 老年代(old generation regions).

剛剛被提升上來的物件用深綠色顯示. Survivor 區用綠色表示.

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

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

Old Generation Collection with G1

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

G1 收集階段 – 併發標記週期階段(Concurrent Marking Cycle Phases)

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 Event,所有應用執行緒暫停) 產生STW事件來轉移或拷貝存活的物件到新的未使用的heap區(new unused regions). 只在年輕代發生時日誌會記錄為 `[GC pause (young)]`. 如果在年輕代和老年代一起執行則會被日誌記錄為 `[GC Pause (mixed)]`.

G1老年代收集步驟

順著定義的階段,讓我們看看G1收集器如何處理老年代(old generation).

6. 初始標記階段(Initial Marking Phase)

存活物件的初始標記被固定在年輕代垃圾收集裡面. 在日誌中被記為 GC pause (young)(inital-mark)

7. 併發標記階段(Concurrent Marking Phase)

如果找到空的區域(如用紅叉“X”標示的區域), 則會在 Remark 階段立即移除. 當然,"清單(accounting)"資訊決定了活躍度(liveness)的計算.

8. 再次標記階段(Remark Phase)

空的區域被移除並回收。現在計算所有區域的活躍度(Region liveness).

9. 拷貝/清理階段(Copying/Cleanup)

G1選擇“活躍度(liveness)”最低的區域, 這些區域可以最快的完成回收. 然後這些區域和年輕代GC在同時被垃圾收集 . 在日誌被標識為 [GC pause (mixed)]. 所以年輕代和老年代都在同一時間被垃圾收集.

10.拷貝/清理之後(After Copying/Cleanup)

所選擇的區域被收集和壓縮到下圖所示的深藍色區域和深綠色區域.

老年代GC(Old Generation GC)總結

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

  • 併發標記清理階段(Concurrent Marking Phase)
    • 活躍度資訊在程式執行的時候被平行計算出來
    • 活躍度(liveness)資訊標識出哪些區域在轉移暫停期間最適合回收.
    • 不像CMS一樣有清理階段(sweeping phase).
  • 再次標記階段(Remark Phase)
    • 使用的 Snapshot-at-the-Beginning (SATB, 開始快照) 演算法比起 CMS所用的演算法要快得多.
    • 完全空的區域直接被回收.
  • 拷貝/清理階段(Copying/Cleanup Phase)
    • 年輕代與老年代同時進行回收.
    • 老年代的選擇基於其活躍度(liveness).

命令列引數與最佳實踐

命令列引數與最佳實踐

在本節中,讓我們看看G1的各種命令列選項.

命令列基本引數

要啟用 G1 收集器請使用: -XX: UseG1GC

下面是啟動 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 作為垃圾收集器時,你應該遵循下面這些最佳實踐的指導.

不要設定年輕代的大小(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)

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

  • 增加堆記憶體大小
    • 增加 -XX:G1ReservePercent=n, 其預設值是 10.
    • G1建立了一個假天花板(false ceiling),在需要更大 ‘to-space’ 的情況下會嘗試從保留記憶體獲取(leave the reserve memory free).
  • 更早啟動標記週期(marking cycle)
  • 通過採用 -XX:ConcGCThreads=n 選項增加標記執行緒(marking threads)的數量.
G1 的 GC 引數完全列表

下面是完整的 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=neden/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。

總結

在此OBE中, 您對Java JVM 中的G1垃圾收集器有了個大致的瞭解。首先你學到了為何堆和垃圾收集器是所有Java JVM的關鍵部分。接下來講述了使用CMS和G1收集器進行垃圾回收的工作方式. 接下來,您瞭解了G1的命令列引數/開關以及和使用它們的最佳實踐。最後,您瞭解了日誌物件以及GC日誌中的資料。

在本教程中,你學到了這些知識:

  • Java JVM 的組成部分
  • 對 G1 的概述
  • 概述 CMS 垃圾收集器
  • 概述 G1 垃圾收集器
  • 命令列引數與最佳實踐
  • G1 的日誌資訊

相關資源

更多相關資訊請參考以下網站連結.

作者資訊

  • 課程開發人員: Michael J Williams
  • 質量保證: Krishnanjani Chitta