g1垃圾回收器與cms垃圾回收器詳解及最佳實踐

 

 

G1垃圾收集器入門

說明

concurrent: 併發, 多個線程協同作同一件事情(有狀態)html

parallel: 並行, 多個線程各作各的事情(互相間無共享狀態)java

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

概述

目的

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

須要的時間

大約 1 個小時github

簡介

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

  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:\javademosapi

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)在併發階段的二、三、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=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。

總結

在此OBE中, 您對Java JVM 中的G1垃圾收集器有了個大體的瞭解。首先你學到了爲什麼堆和垃圾收集器是全部Java JVM的關鍵部分。接下來說述了使用CMS和G1收集器進行垃圾回收的工做方式. 接下來,您瞭解了G1的命令行參數/開關以及和使用它們的最佳實踐。最後,您瞭解了日誌對象以及GC日誌中的數據。

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

  • Java JVM 的組成部分
  • 對 G1 的概述
  • 概述 CMS 垃圾收集器
  • 概述 G1 垃圾收集器
  • 命令行參數與最佳實踐
  • G1 的日誌信息

相關資源

更多相關信息請參考如下網站連接.

做者信息

  • 課程開發人員: Michael J Williams
  • 質量保證: Krishnanjani Chitta
相關文章
相關標籤/搜索