1. 程式人生 > >Java 9 中的 GC 調優基礎

Java 9 中的 GC 調優基礎

在經過了幾次跳票之後,Java 9終於在原計劃日期的整整一年之後釋出了正式版。Java 9引入了很多新的特性,除了閃瞎眼的Module System和REPL,最重要的變化我認為是預設GC(Garbage Collector)修改為新一代更復雜、更全面、效能更好的G1(Garbage-First)。JDK的維護者在GC選擇上一直是比較保守的,G1從JDK 1.6時代就開始進入開發者的視野,直到今天正式成為Hotspot的預設GC,也是走了很長的路。

本文將主要講解GC調優需要知道的一些基礎知識,會涉及到一些GC的實現細節,但不會對實現細節做很全面的闡述,如果你看完本文之後,能對GC有一個大致的認識,那本文的寫作目的也就達到了。由於在這次寫作過程中,恰逢Java 9正式版釋出,之前都是依賴Java 8的文件寫的,如果有不正確的地方還望指正。本文將包含以下內容:

  1. GC的作用範圍

  2. GC負責的事情

  3. JVM中的4種GC

  4. G1的一些細節

  5. 使用Java 9正式版對G1進行測試

  6. 一些簡單的GC調優方法

一、GC 的作用範圍

要談GC的作用範圍,首先要談JVM的記憶體結構,JVM記憶體中主要有以下幾個區域:堆、方法區(JVM規範中的叫法,Hotspot大致對應的是Metaspace)、棧、本地方法棧、PC等,其中GC主要作用在堆上,如下圖所示:

JVM記憶體結構

其中堆和方法區是所有執行緒共享的,其他則為執行緒獨有,HotSpot JVM使用基於分代的垃圾回收機制,所以在堆上又分為幾個不同的區域(在G1中,各年齡代不再是連續的一整片記憶體,為了描述方便,這裡還使用傳統的表示方法),具體如下圖所示:

JVM堆中的分割槽

二、GC 負責的事情

GC的發展是隨著JDK(Standard Edition)的發展一步步發展起來的,垃圾回收(Garbage Collection)可以說是JDK裡最影響效能的行為了。GC做的事情,說白了就是「通過對記憶體進行管理,以保障在記憶體足夠的時候,程式可以正常的使用記憶體」。具體而言,GC通常做的事情有以下3個:

1. 分配物件和物件的年齡管理

通常而言,GC需要管理「在上圖中的年輕代(Young)分配物件,然後通過一系列的年齡管理,將之銷燬或晉升到老年代(Tenured)中去」的過程。這個過程會伴隨著若干次的Minor GC。

對於普通的物件而言,分配記憶體是一件很簡單而且快速的事情。在物件還未建立時,其所佔記憶體大小通過類的元資料就可以確定,而Eden區域的記憶體可以認為是連續的,所以給物件分配記憶體要做的只是在上圖中Eden區域中把指標移動相應的長度,並將地址返回給物件的引用即可。當然實際的過程比這個複雜,在下文中會提到。

不過,有時候一個物件會直接在老年代中建立,這個點也會在後邊提到。

2. 在老年代中進行標記

老年代的GC演算法可以大致是認為是一個標記-整理(Mark-Compact,其實是混合了標記-清理,標記-複製和標記-整理)演算法,所以老年代的垃圾清理首先要做的就是在老年代對存活的物件(可達性分析,關於不同的可達性可以參考JDK解構 – Java中的引用和動態代理的實現)進行標記,對於尋求大吞吐量的伺服器應用來說,這個過程往往需要是併發的。

標記的過程發生在Major GC被觸發之後,不同的GC對於MajorGC的觸發條件和標記過程的實現也不盡相同。

3. 在老年代中進行壓縮

在上一條的基礎上,將還存活的物件進行壓縮(CMS和G1的行為與此有些不同之處),壓縮的過程就是將存活的物件從老年代的起點進行挨個複製,使得老年代維持在一片連續的記憶體中,消除記憶體碎片,對於記憶體分配速度的提升會有很大的幫助。

三、GC 的種類

Hotspot會根據宿主機的硬體特性和作業系統型別,將之分為客戶端型(client-class)或者伺服器型(server-class),如果是伺服器型主機,Java 9之前預設使用Parallel GC,Java 9中預設使用G1。對於伺服器型主機的選擇標準是「CPU核心數大於1,記憶體大於2GB」,所以現在大部分的主機都可以認為是伺服器型主機。

這裡討論的所有GC都是基於分代垃圾回收演算法的。

1. Serail

Serail是最早的一款GC,它只使用一個執行緒來做所有的Minor和Major垃圾回收。它在執行時,其他所有的事情都會暫停。其工作方式十分簡單,在需要GC的安全點,它會停止所有其他執行緒(Stop-The-World),對年輕代進行標記-複製,或對老年代進行標記-整理。

可以使用JVM引數-XX:+UseSerialGC來開啟此GC,當使用此引數時,年輕代和老年代將都是用Serial來做垃圾回收。在年輕代使用標記-複製演算法,將Eden中存活的物件和非空的Suvivor區(From)中存活的物件複製到空的Suvivor區(To)中去,同時將一部分Suvivor中的物件晉升到老年代去。在老年代則使用標記-整理演算法。

看起來Serial古老而簡陋,但在宿主機資源緊張或者JVM堆很小的情況下(比如堆記憶體大小隻有不到100M),Serial反而可以達到更好的效果,因為其他併發或並行GC都是基於多執行緒的,會帶來額外的執行緒切換和執行緒間通訊的開銷。

2. Parallel/Throughput

Parallel在Java 9之前是伺服器型宿主機中JVM的預設GC,其垃圾回收的演算法和Serial基本相同,不同之處在與它使用多執行緒來執行。由於使用了多執行緒,可以享受多核CPU帶來的優勢,可以通過引數-XX:+UseParallelGC -XX:+UseParallelOldGC顯示指定。

3. CMS

CMS和G1都屬於「Mostly Concurrent Mark and Sweep Garbage Collector」,可以使用-XX:+UseConcMarkSweepGC引數開啟。CMS的年輕代垃圾回收使用的是Parallel New來做,其行為和Parallel中的差不多相同,他們的實現上有一些不同的地方,比如Parallel可以自動調節年輕代中各區的大小,用的是廣度優先搜尋等。

老年代使用CMS,CMS的回收和Parallel也基本類似,不同點在與,CMS使用的更復雜的可達性分析步驟,並且不是每次都做壓縮的動作,這樣達到的效果就是,Stop-The-World的時長會降低,JVM執行中斷的時間減少,適合在對延遲敏感的場景下使用。

CMS在Java 9中已經被廢棄,但瞭解CMS的行為對理解G1會有一些幫助,所以這裡還是會簡單的敘述一下。CMS的步驟大致如下:

  1. 第一次標記
    從GC Roots開始,找到它們在老年代中第一個可達的物件,這些物件或者是直接被GC Roots引用,或者通過年輕代中的物件被GC Roots引用。這一步會Stop-The-World。

  2. 併發標記
    在第一次標記的基礎上,進一步進行可達性分析,從而標記存活的物件。這一步叫「併發」標記,是因為做標記的執行緒是和應用的工作執行緒併發執行的,也就是說,這一步不會Stop-The-World。

  3. 第二次標記
    在併發標記的過程中,由於程式仍在執行,會導致在併發標記完成後,有一些物件的可達性會發生變化,所以需要再次對他們進行標記。這一步會Stop-The-World。

  4. 清理
    回收不使用的物件,留作以後使用。

CMS的設計比較複雜,所以也帶來了一些問題,比如浮動垃圾(Floating Garbage,指的是在第一步標記可達,但在第二步執行的同時已經不可達的物件),由於不做老年代壓縮,導致老年代會出現較多的記憶體碎片。

4. G1

由於「引入了併發標記」和「不做老年代壓縮」,CMS可以帶來更好的響應時延表現,但同時也帶來了一些問題。G1本身就是作為CMS的替代品出現的,在它的使用場景裡,堆不再是連續的被分為上文所說的各種代,整個堆會被分為一個個區域(Region),每個區域可以是任何代。如下圖所示:

使用G1的JVM某時刻的堆記憶體

其中有紅色方框的為年輕代(標S的為Survivor區域,其他為Eden),其他藍色底的區域為老年代(標H的為大物件區域,用以儲存大物件)。

四、G1 的一些細節

G1與以上3種GC相同,也是基於分代的垃圾回收器。它的垃圾回收步驟可以分為年輕代回收(Young-only phase,類似於Minor GC)和混合垃圾回收階段(Space-reclamation phase)。下圖是Oracle文件中對於此兩個階段的示意圖:

G1設計目標和適用物件

G1的設計目標是讓大型的JVM可以動態的控制GC的行為以滿足使用者配置的效能目標。G1會在平衡吞吐和響應時延的基礎上,儘可能的滿足使用者的需求。它適用的JVM往往有以下特徵:

  1. 堆的大小可能達到數十G(或者更大),同時存活的物件數量也很多。

  2. 物件的分配和年齡增長的行為隨著程式的執行不斷的變化

  3. 堆上很容易形成碎片

  4. 要求較少的Stop-The-World暫停時間,通常小於數百毫秒

五、對 G1 的行為進行測試

如果想要看垃圾回收的具體執行過程,可以使用虛擬機器引數-Xlog:gc*=debug或者-Xlog:gc*=info,前一個會列印更多的細節。注意傳統的VM引數-XX:+PrintGCDetails在Java9中已經廢棄,會有Warning資訊。可以使用以下程式碼中的程式去測試:

在這段程式碼中,模擬了常規程式的使用情況。不斷的生成新的大小不等的物件,這些物件中會有大約10%的機會進入浮動垃圾floatingObjs,浮動垃圾會被定期清除。同時會有一部分的物件進入immortalObjs,這些物件被釋放的機會更少,它們大概率將成為老年代的常住使用者。

從上邊的測試可以得到如下GC日誌1,這是一次完整的年輕代GC,從中可以看到,預設的區域大小為1M,同時將開始一次Full GC,其格式大致為[<虛擬機器執行的時長>][<日誌級別>][<標籤>] GC(<GC的標識>) <其他資訊>

年輕代回收(Young-only)

對於純粹的年輕代回收,其演算法很簡單,與Parallel和CMS的年輕代十分類似,這是一個多執行緒並行執行的過程,同樣需要Stop-The-World(對應上邊日誌中的Pause Young),停下來所有的工作執行緒,然後將Eden上存活的物件拷貝到Suvivor區域,這裡會將很多個物件從多個不同的區域拷貝到少數的幾個區域內,所以這一步在G1中叫做疏散(Evacuation),同時把Suvivor上觸及年齡閾值的物件晉升到老年代區域。

老年代回收(concurrent cycle)

G1的老年代回收是在老年代空間觸及一個閾值(Initiating Heap Occupancy Percent)之後,這個回收伴隨著年輕代的回收工作,但與上邊所說的回收有些不同。

  1. 年輕代回收:伴隨著年輕代的回收工作,同時會執行併發標記和一部分清理的工作,這樣可以共用年輕代垃圾回收的Stop-The-World。

    1. 第一次標記:對應一次Pause Initial Mark
      和CMS的步驟類似,首先進行第一次標記。但實現方法上有很大的區別,G1首先對當前堆上的物件情況進行一個虛擬快照(Snapshot-At-The-Beginning),然後根據這個快照對老年代的物件和區域進行標記,並執行之後的垃圾回收。之後像CMS一樣會有併發標記的過程。
      這樣會產生一個問題,在這次回收結束之後,會有些物件在併發標記的過程中,它的可達性已經變化,導致已經不可達的物件仍然沒有被回收。但是這樣能帶來更好的響應時間。

    2. 重新標記:對應一次Pause Remark
      在這個階段,G1首先完成上一步開始的標記工作,之後會對特殊引用的物件進行處理(具體可以參考JDK解構 – Java中的引用和動態代理的實現),還有對Metaspace區域進行垃圾回收。這一步會進行Stop-The-World。

    3. 清理:對應一次Pause Cleanup
      這一步主要做的是收集當前堆中的記憶體區域資訊,對空的區域進行回收,為接下來的空間回收做一些準備工作,清理結束之後,通常會伴隨著一次年輕代回收,如果判斷不需要進行空間回收,則會進入下一個年輕代回收的工作。這一步會進行Stop-The-World。

  2. 混合垃圾回收:對應一次或多次Pause Mixed
    主要做的是對老年代的區域記憶體進行疏散(Evacuation),也包含對年輕代的區域回收工作。同時這一步也會動態地調整IHOP

從對G1的GC日誌的分析,可以看到G1的垃圾回收行為是基於一個可預測的模型:GC會不斷的主動觸發垃圾回收,在這個過程中不斷地進行資訊統計和系統GC引數的設定,然後將上邊這些步驟安排在這些垃圾回收過程中。

大物件的分配

正常情況下,一個物件會在年輕代的Eden中建立,然後通過垃圾回收和年齡管理之後,晉升到老年代。但對於某些比較大的物件,可能會直接分配到老年代去。

對於G1,物件大多數情況都會在Eden上分配,如果JVM判斷一個物件為大物件(其閾值可以通過-XX:G1HeapRegionSize來設定),則會直接分配如老年代的大物件區域中。

對於其他的記憶體區域連續的GC,下面是從StackOverflow上搬運過來的物件在堆上的分配過程:

  1. 使用 thread local allocation buffer (TLAB), 如果空間足夠,則分配成功。
    從名稱便可知,TLAB是執行緒獨佔的,所以執行緒安全,且速度非常快。如果一個TLAB滿了,執行緒會被分配一個新的TLAB。

  2. 如果TLAB 空間不夠這次分配物件,但其中還有很多空間可用,則不使用TLAB,直接在Eden中分配物件。
    直接在Eden上分配物件要去搶佔Eden中的指標操作,其代價較使用TLAB要大一些。

  3. 如果Eden的物件分配失敗,出發Minor GC。

  4. 如果Minor GC完成後還不夠,則直接分配到老年代。

六、一些簡單的GC調優方法

1. 使用不同的索引物件

引用的型別會直接影響其所引用物件的GC行為,當要做一些記憶體敏感的應用時,可以參考使用合適的引用型別。具體可以參考JDK解構 – Java中的引用和動態代理的實現。

2. 使用Parallel

從上文中可知,Java 8預設的GC是Parallel,它也叫Throughput,所以它的目的是儘可能的增加系統的吞吐量。在Parallel裡,可以通過引數調節最大停止時間(-XX:MaxGCPauseMillis,預設無設定)和吞吐量(-XX:GCTimeRatio,預設值是99,即最大使用1%的時間來做垃圾回收)來調優GC的行為。其中設定最大停止時間可能會導致GC調節各年齡代分割槽的尺寸(通過增量來實現)。

3. 使用G1

從Java 9開始G1變成了預設的GC,G1中有一些細節的概念在上文中沒有敘述,這裡先介紹一下:

  1. Remembered Sets(Rsets):對於每個區域,都有一個集合記錄這個區域中所有的引用。

  2. G1 refinement:G1中需要有一系列的執行緒不斷地維護Rsets。

  3. Collection Sets(Csets):在垃圾回收中需要被回收的區域,這些區域中的可達物件(活著的物件)會被疏散。這些區域可能是任何年齡代。

  4. 寫屏障(Write Barriers):對於每一次賦值操作,G1都會有兩個寫屏障,寫之前(Pre-Write)一個,寫之後(Post-Write)一個。Pre-write主要與SATB相關,Post-write主要與Rsets相關

  5. Dirty Card Queue:寫屏障會將寫的記錄放入這個佇列,會有執行緒將這裡的物件不斷的刷入Rsets。

  6. Green/Yellow/Red Zone:三個會影響處理Dirty Card Queue執行緒數的閾值。根據Dirty Card Queue中元素的個數,可以來設定一些GC行為(可以認為是邏輯上將Dirty Card Queue分隔成多個區域)。Green表示超過此閾值則開始新建執行緒來處理這個佇列,Yellow表示超過此閾值,強制啟動這些執行緒,Red表示超過此閾值則會讓寫操作的執行緒自己來執行G1 refinement。

G1提供了豐富的基於不同目的的可調優的引數,列表如下:

主要參考文件:

  1. Getting Started with the G1 Garbage Collector:

  2. Garbage-First Garbage Collector Tuning

  3. Evaluating and improving remembered sets in the HotSpot G1 garbage collector

  4. G1GC Internals

  5. GC Algorithms: Basics

  6. Java中幾種常量池的區分


相關推薦

Java 9 GC 調基礎

在經過了幾次跳票之後,Java 9終於在原計劃日期的整整一年之後釋出了正式版。Java 9引入了很多新的特性,除了閃瞎眼的Module System和REPL,最重要的變化我認為是預設GC(Garbage Collector)修改為新一代更復雜、更全面、效能更好的G1(Gar

5. GC 調(基礎篇)

說明: Capacity: 效能,能力,系統容量; 文中翻譯為”系統容量“; 意為硬體配置。 您應該已經閱讀了前面的章節: GC調優(Tuning Garbage Collection)和其他效能調優是同樣的原理。初學者可能會被 200

java虛擬機-GC-調

最小 次數 n) 選擇 內存碎片 tails 最優 統計 收集器 1. 年輕代大小選擇 * 響應時間優先的應用:盡可能設大,直到接近系統的最低響應時間限制(根據實際情況選擇)。在此種情況下,年輕代收集發生的頻率也是最小的。同時,減少到達年老代的對象。 * 吞吐量優

GC調在Spark應用的實踐(轉載)

avg fix 時也 net aso 會有 介紹 完整 頻繁 Spark是時下非常熱門的大數據計算框架,以其卓越的性能優勢、獨特的架構、易用的用戶接口和豐富的分析計算庫,正在工業界獲得越來越廣泛的應用。與Hadoop、HBase生態圈的眾多項目一樣,Spark的運行離不開J

Java GC調

元空間 閾值 含義 nor mod 目標 還需 業務安全 占用空間 當Java程序性能達不到既定目標,且其他優化手段都已經窮盡時,通常需要調整垃圾回收器來進一步提高性能,稱為GC優化。但GC算法復雜,影響GC性能的參數眾多,且參數調整又依賴於應用各自的特點,這些因素很大程度

性能測試系列-java gc調

操作 發生 失敗 應用 目標 社區 為什麽 不知道 current 性能測試中除了需要做好性能測試外,我們還需要做性能測試後的,性能調優,需要發現性能問題,也需要做性能調優,在做性能調優中,jvm的性能調優是經常遇到的一個。 隨著jdk版本的迅速變化,jdk裏面的GC算法也

效能測試系列-java gc調

效能測試中除了需要做好效能測試外,我們還需要做效能測試後的,效能調優,需要發現效能問題,也需要做效能調優,在做效能調優中,jvm的效能調優是經常遇到的一個。 隨著jdk版本的迅速變化,jdk裡面的GC演算法也是發生了很多變化,新版的jdk中,G1的已經成了jdk的預設演算法了,效能測試中,我們經常關注的比較多

JVM GC調(2)-----GC演算法判定物件可以被回收(部分摘自深入理解Java虛擬機器)

這次我們介紹JVM中的GC演算法 引用計數法 可達性分析法 首先我們提出四個問題 哪裡的記憶體需要回收? 什麼時候回收? 如何回收? Java與C++之間有一堵由記憶體動態分配和垃圾收集技術所圍成的“高牆”, 牆外面的人想進去, 牆裡面的人卻想出來。

JVM GC調(3)-----GC演算法(部分摘自深入理解Java虛擬機器)

介紹幾種GC演算法的思想及其發展過程: 標記-清除 複製 標記-壓縮 分代收集演算法 GC演算法主要是用於堆死亡物件的清理的集中方式,他們各有優缺點,下面我們開始做介紹 標記清除演算法 原理解析 -最基礎的收集演算法是“標記-清除”( Mark-Swe

針對HBase的Java GC調

文章是由Intel的Java效能架構師(Java performance architect)Eric Kaczmared發表,用於探索如何對HBase進行Java GC調優,全文的測試基於YCSB 100% Read進行測試。 Apache HBase是一個有Apache基金會開源,提供Nos

JAVA GC調零碎記錄

是否需要進行GC調優 決定是否進行Java GC調優,前提是主要看新生代的Minor GC和老年代的FULL GC 的GC頻率和每次GC停頓的時間對於業務來說是否可接受,一般滿足以下條件,可不用考慮GC調優(僅參考,具體還要看業務) 1      MinorGC執行的很快

Java9GC 調

垃圾收集器與記憶體分配策略參考目錄: 在經過了幾次跳票之後,Java 9終於在原計劃日期的整整一年之後釋出了正式版。Java 9引入了很多新的特性,除了閃瞎眼的Module System和REPL,最重要的變化我認為是預設GC(Garbage Collector)修改為新一代更復雜、更全面、效能更

Java 9 9 個新特性

不想 行為 添加元素 ase 結合 quest 簡單 通過 this Java 8 發布三年多之後,即將快到2017年7月下一個版本發布的日期了。 你可能已經聽說過 Java 9 的模塊系統,但是這個新版本還有許多其它的更新。 這裏有九個令人興奮的新功能將與 Java 9

6. GC 調(工具篇) - GC參考手冊

統一 行為 png 永久代 搜索 最大值 自己 跟蹤 err 進行GC性能調優時, 須要明白了解, 當前的GC行為對系統和用戶有多大的影響。有多種監控GC的工具和方法, 本章將逐一介紹經常使用的工具。 您應該已經閱讀了前面的章節: 垃圾收集簡單介

Spark日誌分析項目Demo(9)--常規性能調

array ack 不一定 集合類型 -s 如果 一次 puts cluster 一 分配更多資源 分配更多資源:性能調優的王道,就是增加和分配更多的資源,性能和速度上的提升,是顯而易見的;基本上,在一定範圍之內,增加資源與性能的提升,是成正比的;寫完了一個復雜的spark

Java jvm內存調(來自小強公開課)

jvmJava jvm內存調優 Jdk:java開發工具包Jre:java運行環境,運行你編寫的java程序Jvm:java虛擬機,.class文件在虛擬機上運行 如何選擇合適的java虛擬機》》》選擇穩定的jdk(慎用新出的,新特性的)》》》根據平臺和應用,選擇合適廠商的jdk。HP-UX只能選擇HP J

Java遠程過程調基礎:構建可自適應的動態代理對象的通用方法

Java RPC 動態代理 [toc] Java遠程過程調用基礎:構建可自適應的動態代理對象的通用方法 前言 關於動態代理的知識,這裏不再過多說明。這裏要介紹的是,如何創建一個可自適應任意接口的動態代理對象的通用方法,也就是說,引用對象可為任意接口,舉個例子,假如返回動態代理對象的方法是getP

【深度學習篇】--神經網絡調

flow 初始 clas 根據 叠代 pan 容易 組合 語音 一、前述 調優對於模型訓練速度,準確率方面至關重要,所以本文對神經網絡中的調優做一個總結。 二、神經網絡超參數調優 1、適當調整隱藏層數對於許多問題,你可以開始只用一個隱藏層,就可以獲得不錯的結果,比如對於復雜

記一次線上gc調的過程

aspect hash 接下來 JD lac abs rac 數據庫 %x 近期公司運營同學經常表示線上我們一個後臺管理系統運行特別慢,而且經常出現504超時的情況。對於這種情況我們本能的認為可能是代碼有性能問題,可能有死循環或者是數據庫調用次數過多導致接口運

Spark性能調-基礎

以及 sce 集合 table 團隊 加載 分析 功能 serializa 前言 在大數據計算領域,Spark已經成為了越來越流行、越來越受歡迎的計算平臺之一。Spark的功能涵蓋了大數據領域的離線批處理、SQL類處理、流式/實時計算、機器學習、圖計算等各種不同類型的計算操