一文徹底弄懂Java虛擬機G1垃圾收集器(詳細,面試必看)

垃圾收集器兩個重要的指標

- 吞吐量

  • 吞吐量關注的是在一個指定的時間內,最大化一個應用的工作量
  • 如下方式來衡量一個系統吞吐量的好壞:
    1、在一小時內同一個事務(或者任務、請求)完成的次數(tps)
    2、數據庫一小時可以完成多少次查詢
  • 對於關注吞吐量的系統,卡頓是可以接受的,因爲這個系統關注長時間的大量任務的執行能力,單詞快速的響應並不值得考慮

- 響應能力

  • 響應能力指一個程序或者系統對請求是否能夠及時響應,比如:
    1、一個桌面UI能多快地響應一個時間
    2、一個網站能夠多快返回一個頁面請求
    3、數據庫能夠多快返回查詢的數據
  • 對於這類對響應能力敏感的場景,長時間的停頓是無法接受的

對於Java應用的調優,主要就集中於這兩個指標

爲了很好的滿足這兩個指標,G1垃圾收集器應運而生

G1 Garbage Collector

G1收集器是一個面向服務端的非實時垃圾收集器,適用於多核處理器、大內存容量的服務端系統,它滿足短時間GC停頓的同時達到一個較高的吞吐量,JDK 7以上版本適用

- G1收集器的設計目標

  • 與應用線程同時工作,幾乎不需要STW(與CMS類似)
  • 整理剩餘空間,不產生內存碎片(CMS只能在Full GC時,用STW整理內存碎片)
  • GC停頓更加可控
  • 不犧牲系統的吞吐量
  • GC不要求額外的內存空間(CMS需要預留空間存儲浮動垃圾)

- G1的設計規劃是要替換掉CMS

  • G1在某些方面彌補了CMS的不足,比如CMS基於標記-清除算法,會產生內存碎片,而G1基於複製算法,高效的整理剩餘內存,不需要管理內存碎片
  • 另外,G1提供了更多手段以達到對GC停頓時間的可控

- Hotspot虛擬機主要構成

在這裏插入圖片描述
JVM的性能主要由Heap、JIT Compiler、Garbage Collector控制,堆是對象存儲的位置,這個區域主要由JVM啓動時所選擇的垃圾收集器所管理。大多數調優操作都是和調整堆的大小有關並且選擇最適合當前形勢的垃圾收集器。JIT Compiler對性能也有很大的影響,但是在JVM的新版本中很少需要對JIT Compiler進行調優

- 傳統垃圾收集器堆結構

在這裏插入圖片描述
注:永久代在JDK 8後進行了移除,並用元空間進行替換

- G1收集器堆結構

在這裏插入圖片描述

  • 堆(Heap)被劃分爲一個個相等的不連續的內存區域(Regions),每個region都有一個分代的角色:Eden、Survivor、Old
  • 對每個角色的數量並沒有強制的限定,也就是說對每種分代內存的大小,可以動態變化
  • G1最大的特點就是高效的執行回收,優先去執行那些大量對象可回收的region
  • G1使用了GC停頓可預測的模型,來滿足用戶設定的GC停頓時間,根據用戶設定的目標時間,G1會自動地選擇哪些region要清除,一次清除多少個region
  • G1從多個region中複製存活的對象,然後集中放入一個region中,同時整理、清除內存(複製收集算法 )
  • 新生代和老年代是同時被回收的

    - G1 Collector & Traditional Collector

  • 對比使用標記-清除算法的CMS,G1使用複製算法不會造成內存碎片
  • 對比Parallel Scavenge(基於複製算法)、Parallel Old收集器(基於標記-整理算法),Parallel會對整個區域做整理,從而導致GC停頓會比較長,而G1只是特定地整理幾個region
  • G1並非一個實時的收集器,與parallel Scavenge一樣,對GC停頓時間的設置並不絕對生效,只是G1有較高的機率保證不超過設定的GC停頓時間。與之前的GC收集器對比,G1會根據用戶設定的GC停頓時間,智能評估哪幾個region需要被回收從而滿足用戶的設定

- G1重要概念

  • 分區(Region):G1採取了不同的策略來解決並行、串行和CMS收集器的碎片、暫停時間不可控等問題——G1將整個堆分成相同大小的region,每個分區都可能是新生代或者是老年代,但是在同一時刻只能屬於某個代。新生代、倖存區、老年代這些概念還存在,成爲邏輯上的概念,這樣方便複用之前分代框架的邏輯,在物理上不需要連續,因此帶來了額外的好處——有的region內垃圾對象特別多,G1會優先回收這些region,這樣可以花費較少的時間來回收這些分區的垃圾,這也是G1名字的由來,即首先收集垃圾最多的region。依然是在新生代滿了的時候對整個新生代進行回收——整個新生代中的對象,要麼被回收、要麼晉升,至於新生代也採取分區機制的原因,則是因爲這樣跟老年代的策略統一,方便調整代的大小。G1還是一種帶壓縮的收集器,在回收老年代的分區時,是將存活的對象從一個分區拷貝到另一個可用分區,這個拷貝的過程就實現了局部的壓縮
  • 收集集合(CSet):一組可被回收的分區的集合。在CSet中存活的數據會在GC過程中被移動到另一個可用區間,CSet中的分區可以來自Eden空間、Survivor空間、或者Old空間
  • 已記憶集合(RSet):RSet記錄了其他region中的對象引用本region中對象的關係,屬於points-into結構(誰引用了我的對象)。RSet的價值在於使得垃圾收集器不需要掃描整個堆找到誰引用了當前分區中的對象,只需要掃描RSet即可
    在這裏插入圖片描述
    Region1和Region3中的對象都引用了Region2中的對象,因此在Region2的RSet中記錄了這兩個引用
  • Snapshot-At-The-Beginning(SATB):SATB是G1 GC在併發標記階段使用的增量式的標記算法,併發標記是併發多線程的,但併發線程在同一時刻只掃描一個region

在這裏插入圖片描述

- G1相對於CMS的優勢

  • G1在壓縮空間方面有優勢
  • G1通過將內存空間分成區域(Region)的方式避免內存碎片問題
  • Eden、Survivor、Old區不再固定,在內存使用效率上來說更靈活
  • G1可以通過設置預期停頓時間(Pause Time)來控制垃圾收集時間,避免應用雪崩現象
  • G1在回收內存後會馬上同時做合併空閒內存的工作,而CMS默認是在STW的時候做
  • G1會在Young GC中使用,而CMS只能在老年代中使用

- G1的適合場景

  • 服務端多核CPU、JVM內存佔用較大的應用
  • 應用在運行過程中會產生大量內存碎片、需要經常壓縮空間
  • 想要更可控、可預期的GC停頓週期,防止高併發下應用的雪崩現象

- G1 GC模式

G1提供了兩種GC模式——Young GC和Mixed GC,兩種都是完全Stop The World

  • Young GC:選定所有新生代裏的region,通過控制新生代的region個數,即新生代內存大小,來控制Young GC的時間開銷
  • Mixed GC:選定所有新生代裏的region,外加根據global concurrent marking統計得出收集收益高的若干老年代region(垃圾對象更多的老年代區域)。在用戶指定的開銷目標範圍內儘可能選擇收益高的老年代region

Mixed GC不是Full GC,它只能回收部分老年代的region,如果Mixed GC實在無法跟上程序分配內存的速度,導致老年代填滿,無法繼續進行Mixed GC,就會使用serial old GC(Full GC)來收集整個GC heap,所以本質上G1是不提供Full GC的

- global concurrent marking

global concurrent marking的執行過程類似於CMS,但是不同的是,在G1 GC中,它主要是爲Mixed GC提供標記服務的,並不是一次GC過程的一個必須環節

global concurrent marking的執行過程分爲四個步驟:

  • 初始標記【STW】:它標記了從GC Root開始直接可達的對象
  • 併發標記:這個階段從GC Root開始對heap中的對象進行標記,標記線程與應用程序線程併發執行,並且收集各個region的存活對象信息
  • 重新標記【STW】:標記那些在併發標記階段發生變化的對象,將被回收
  • 清理:清除空region(沒有存活對象),加入到free list

初始標記是共用了Young GC的暫停,只是因爲它們可以複用root scan操作,所以說global concurrent marking是伴隨Young GC而發生的,清理只是回收了沒有存活對象的region,所以它不需要STW

- G1在運行過程中的主要模式

  • YGC(不同於CMS):在Eden充滿時觸發,在回收之後所有之前屬於Eden的區域全部變成空白,即不屬於任何一個分區(Eden、Survivor、Old)
  • 併發階段
  • 混合模式
  • Full GC(一般是G1出現問題時發生)

- 什麼時候發生Mixed GC?

由一些參數控制,另外也控制着哪些老年代region會被納入到CSet(收集集合)

  • G1HeapWastePercent:在global concurrent marking結束之後,我們可以知道老年代區域中有多少空間要被回收,在每次Young GC之後和再次發生Mixed GC之前,會檢查垃圾佔比是否達到此參數,只有達到了,下次纔會發生Mixed GC
  • G1MixedGCLiveThresholdPercent:老年代區域中的存活對象的佔比小於此參數,也就是老年代區域中垃圾對象的佔比超過此參數時,纔會被選入CSet
  • G1MixedGCCountTarget:一次global concurrent marking之後,最後執行Mixed GC的次數
  • G1OldCSetRegionThresholdPercent:一次Mixed GC中能被納入到CSet的最多老年代region的數量
    在這裏插入圖片描述

- G1收集概覽

  • G1算法將堆劃分爲若干個區域(Region),它仍然屬於分代收集器。不過,這些區域的一部分包含新生代,新生代的垃圾收集依然採用暫停所有應用線程的方式,將存活對象拷貝到老年代或者Survivor空間。老年代也分成很多區域,G1收集器通過將對象從一個區域複製到另外一個區域,完成了清理工作。這就意味着,在正常的處理過程中,G1完成了堆的壓縮(至少是部分堆的壓縮),這樣也就不會有cms內存碎片問題的存在了

- Humongous區域

  • 在G1中,還有一種特殊的區域,叫Humongous區域。 如果一個對象佔用的空間超過了分區容量50%以上,G1收集器就認爲這是一個巨型對象。這些巨型對象,默認直接會被分配在老年代,但是如果它是一個短期存在的巨型對象,就會對垃圾收集器造成負面影響。爲了解決這個問題,G1劃分了一個Humongous區,它用來專門存放巨型對象。如果一個Humongous區裝不下一個巨型對象,那麼G1會尋找連續的Humongous分區來存儲。爲了能找到連續的H區,有時候不得不啓動Full GC

- G1 Young GC

  • Young GC主要是對Eden區進行GC,它在Eden空間耗盡時會被觸發。在這種情況下,Eden空間的數據移動到Survivor空間中,如果Survivor空間不夠,Eden空間的部分數據會直接晉升到年老代空間。Survivor區的數據移動到新的Survivor區中,也有部分數據晉升到老年代空間中最終Eden空間的數據爲空,GC停止工作,應用線程繼續執行(前面整個過程都是STW的)
    在這裏插入圖片描述

  • 這時,我們需要考慮一個問題,如果僅僅GC新生代對象,我們如何找到所有的根對象呢? 老年代的所有對象都是根麼?那這樣掃描下來會耗費大量的時間。於是,G1引進了RSet的概念,它的全稱是Remembered Set,作用是跟蹤指向某個heap區內的對象引用
    在這裏插入圖片描述
    在CMS中,也有RSet的概念,在老年代中有一塊區域用來記錄指向新生代的引用。這是一種point-out,在進行Young GC時,掃描根時,僅僅需要掃描這一塊區域,而不需要掃描整個老年代

  • 但在G1中,並沒有使用point-out,這是由於一個分區太小,分區數量太多,如果是用point-out的話,會造成大量的掃描浪費,有些根本不需要GC的分區引用也掃描了

  • 於是G1中使用point-into來解決。point-into的意思是哪些分區引用了當前分區中的對象。這樣,僅僅將這些對象當做根來掃描就避免了無效的掃描

  • 由於新生代有多個,那麼我們需要在新生代之間記錄引用嗎?這是不必要的,原因在於每次GC時,所有新生代都會被掃描,所以只需要記錄老年代到新生代之間的引用即可

  • 需要注意的是,如果引用的對象很多,賦值器需要對每個引用做處理,賦值器開銷會很大,爲了解決賦值器開銷這個問題,在G1 中又引入了另外一個概念,卡表(Card Table)。一個Card Table將一個分區在邏輯上劃分爲固定大小的連續區域,每個區域稱之爲卡。卡通常較小,介於128到512字節之間。Card Table通常爲字節數組,由Card的索引(即數組下標)來標識每個分區的空間地址

  • 默認情況下,每個卡都未被引用。當一個地址空間被引用時,這個地址空間對應的數組索引的值被標記爲”0″,即標記爲髒被引用,此外RSet也將這個數組下標記錄下來。一般情況下,這個RSet其實是一個Hash Table,Key是別的Region(即引用當前region的region)的起始地址,Value是一個集合,裏面的元素是Card Table的Index

  • Young GC 可以分爲如下5個階段:
    階段1:根掃描——靜態和本地對象被掃描
    階段2:更新RS——處理dirty card隊列更新RS
    階段3:處理RS——檢測從年輕代指向年老代的對象
    階段4:對象拷貝——拷貝存活的對象到survivor/old區域
    階段5:處理引用隊列——軟引用,弱引用,虛引用處理

- 再談G1 Mixed GC

Mixed GC不僅進行正常的新生代垃圾收集,同時也回收部分後臺掃描線程標記的老年代分區

它的GC步驟分爲兩步:

  • 全部併發標記(global concurrent marking)
  • 拷貝存活對象(evacuation)

在G1 GC中,它主要是爲Mixed GC提供標記服務的,並不是一次GC過程的一個必須環節。global concurrent marking的執行過程分爲四個步驟,具體步驟見上文

三色標記算法

提到併發標記,我們不得不瞭解併發標記的三色標記算法。它是描述追蹤式回收器的一種有用的方法,利用它可以推演回收器的正確性。

我們將對象分成三種類型的:

  • 黑色:根對象,或者該對象與它的子對象都被掃描(對象被標記了,且它的所有field【如成員變量等】也被標記完了)
  • 灰色:對象本身被掃描,但還沒掃描完該對象中的子對象(它的field還沒有被標記或標記完)
  • 白色:未被掃描對象,掃描完成所有對象之後,最終爲白色的爲不可達對象,即垃圾對象(對象沒有被標記到)

- 三色標記算法詳解

  • 根對象被置爲黑色,子對象被置爲灰色
    在這裏插入圖片描述
  • 繼續由灰色遍歷,將已掃描了子對象的對象置爲黑色
    在這裏插入圖片描述
  • 遍歷了所有可達的對象後,所有可達的對象都變成了黑色。不可達的對象即爲白色,需要被清理
    在這裏插入圖片描述

上面的過程看起來沒有什麼問題,但是如果在標記過程中,應用程序也在運行,那麼對象的指針就有可能改變。這樣的話,我們就會遇到一個問題:對象丟失問題

  • 當垃圾收集器掃描到下面情況時
    在這裏插入圖片描述
  • 這時候應用程序執行了如下操作
A.c = C;
B.c = null;
  • 此時對象的狀態圖變成如下情形
    在這裏插入圖片描述
  • 這時候垃圾收集器再標記掃描的時候就會導致下面這種情況,此時C是白色,而A已經是黑色的狀態了,C被漏標了,被垃圾收集器認爲是垃圾需要被清理掉,顯然這是不合理的
    在這裏插入圖片描述

- SATB

  • 在開始標記的時候生成一個快照圖,標記存活對象
  • 在併發標記的時候所有被改變的對象入隊(在write barrier裏把所有舊的引用所指向的對象都變成非白的)——解決漏標問題
  • 可能存在浮動垃圾,將在下次被收集

- G1混合式回收

  • G1到現在可以知道哪些老的分區可回收垃圾最多。 當全局併發標記完成後,在某個時刻,就開始了Mixed GC。這些垃圾回收被稱作“混合式”是因爲他們不僅僅進行正常的新生代垃圾收集,同時也回收部分後臺掃描線程標記的分區
    在這裏插入圖片描述
  • Mixed GC也是採用的複製清理策略,當GC完成後,會重新釋放空間
    在這裏插入圖片描述

- G1分代算法

  • 爲老年代設置分區的目的是老年代裏有的分區垃圾多,有的分區垃圾少,這樣在回收的時候可以專注於收集垃圾多的分區,這也是G1名稱的由來。不過這個算法並不適合新生代垃圾收集,因爲新生代的垃圾收集算法是複製算法,但是新生代也使用了分區機制主要是因爲便於代大小的調整

- SATB詳解

  • SATB是維持併發GC的一種手段。G1併發的基礎就是SATB。SATB可以理解成在GC開始之前對堆內存裏的對象做一次快照,此時活的對像就認爲是活的,從而開成一個對象圖
  • 在GC收集的時候,新生代的對象也認爲是活的對象,除此之外其他不可達的對象都認爲是垃圾對象

如何找到在GC過程中分配的對象呢?

  • 每個region記錄着兩個top-at-mark-start(TAMS)指針,分別爲prevTAMSnextTAMS。在TAMS以上的對象就是新分配的,因而被視爲隱式marked。通過這種方式我們就找到了在GC過程中新分配的對象,並把這些對象認爲是活的對象

解決了對象在GC過程中分配的問題,那麼在GC過程中引用發生變化的問題怎麼解決呢?

  • G1給出的解決辦法是通過Write BarrierWrite Barrier就是對引用字段進行賦值做了額外處理。通過Write Barrier就可以瞭解到哪些引用對象發生了什麼樣的變化
  • mark的過程就是遍歷heap標記live object的過程,採用的是三色標記算法,這三種顏色爲white(表示還未訪問到)、gray(訪問到但是它用到的引用還沒有完全掃描)、black(訪問到而且其用到的引用已經完全掃描完)
  • 整個三色標記算法就是從GC roots出發遍歷heap,針對可達對象先標記white爲gray,然後再標記gray爲black;遍歷完成之後所有可達對象都是balck的,所有white都是可以回收的
  • SATB僅僅對於在marking開始階段進行“snapshot”(marked all reachable at mark start),但是concurrent的時候併發修改可能造成對象漏標記

三色標記算法可能造成漏標的幾種情形:

  • 對black新引用了一個white對象,然後又從gray對象中刪除了對該white對象的引用,這樣會造成了該white對象漏標記
  • 對black新引用了一個white對象,然後從gray對象刪了一個引用該white對象的white對象,這樣也會造成了該white對象漏標記
  • 對black新引用了一個剛new出來的white對象,沒有其他gray對象引用該white對象,這樣也會造成了該white對象漏標記

對於三色算法在concurrent的時候可能產生的漏標記問題,SATB在marking階段中,對於從gray對象移除的目標引用對象標記爲gray,對於black引用的新產生的對象標記爲black;由於是在開始的時候進行snapshot,因而可能存在Floating Garbage

漏標與誤標:誤標沒什麼關係,頂多造成浮動垃圾,在下次GC還是可以回收的,但是漏標的後果是致命的,把本應該存活的對象給回收了,從而影響的程序的正確性

  • 漏標的情況只會發生在白色對象中,且滿足以下任意一個條件
    1、併發標記時,應用線程給一個黑色對象的引用類型字段賦值了該白色對象
    2、併發標記時,應用線程刪除所有灰色對象到該白色對象的引用,但此時可能有黑色對象引用該白色對象
  • 對於第一種情況,利用post-write barrier,記錄所有新增的引用關係,然後根據這些引用關係爲根重新掃描一遍
  • 對於第二種情況,利用pre-write barrier,將所有即將被刪除的引用關係的舊引用記錄下來,最後以這些舊引用爲根重新掃描一遍

- 停頓預測模型

  • G1收集器突出表現出來的一點是通過一個停頓預測模型根據用戶配置的停頓時間來選擇CSet的大小,從而達到用戶期待的應用程序暫停時間
  • 通過-XX:MaxGCPauseMillis參數來設置。這一點有點類似於ParallelScavenge收集器。 關於停頓時間的設置並不是越短越好
  • 設置的時間越短意味着每次收集的CSet越小,導致垃圾逐步積累變多,最終不得不退化成Serial GC(出現STW,進行Full GC);停頓時間設置的過長,那麼會導致每次都會產生長時間的停頓,影響了程序對外的響應時間

- G1收集模式總結

  • Young GC:收集新生代裏的region
  • Mixed GC:新生代的所有region + 全局併發標記階段選出的收益高的老年代region
  • 無論是Young GC還是Mixed GC都只是併發拷貝的階段

分代GC模式下選擇CSet有兩種子模式,分別對應Young GC和Mixed GC

  • Young GC:CSet就是所有年輕代裏面的region
  • Mixed GC:CSet是所有新生代的region加上在全局併發標記階段標記出來的收益高的region

- G1的運行過程

  • 會在Young GC和Mixed GC之間不斷地切換運行,同時定期地做全局併發標記,在實在趕不上對象創建速度的情況下 使用Full GC(Serial GC)
  • 初始標記是在Young GC.上執行的,在進行全局併發標記的時候不會做Mixed GC,在做Mixed GC的時候也不會啓動初始標記階段
  • 當Mixed GC趕不上對象產生的速度的時候就退化成Full GC,這一點是需要重點調優的地方

G1最佳實踐

- 不斷調優暫停時間指標

  • 通過XX:MaxGCPauseMillis=x可以設置啓動應用程序暫停的時間,G1在運行的時候會根據這個參數選擇CSet來滿足響應時間的設置。一般情況下這個值設置到100ms或者200ms都是可以的(不同情況下會不一樣),但如果設置成50ms就不太合理。暫停時間設置的太短,就會導致出現G1跟不上垃圾產生的速度,最終退化成Full GC。所以對這個參數的調優是一個持續的過程,逐步調整到最佳狀態

- 不要設置新生代和老年代的大小

  • G1收集器在運行的時候會調整新生代和老年代的大小。通過改變代的大小來調整對象晉升的速度以及晉升年齡,從而達到我們爲收集器設置的暫停時間目標
  • -設置了新生代大小相當於放棄了G1爲我們做的自動調優。我們需要做的只是設置整個堆內存的大小,剩下的交給G1自己去分配各個代的大小即可。

- 關注Evacuation Failure

  • Evacuation Failure類似於CMS裏面的晉升失敗,堆空間的垃圾太多導致無法完成Region之間的拷貝,於是不得不退化成Full GC來做一次全局範圍內的垃圾收集

G1回收器日誌內容分析

- 測試用例

VM Option:-verbose:gc -Xms10m -Xmx10m -XX:+UseG1GC -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:MaxGCPauseMillis=200m
public class MyTest1 {
    public static void main(String[] args) {
        int size = 1024 * 1024;

        byte[] myAlloc1 = new byte[size];
        byte[] myAlloc2 = new byte[size];
        byte[] myAlloc3 = new byte[size];
        byte[] myAlloc4 = new byte[size];

        System.out.println("hello world");
    }
}

輸出結果:

2020-03-26T10:10:49.680+0800: [GC pause (G1 Humongous Allocation) (young) (initial-mark), 0.0016222 secs]
   [Parallel Time: 1.1 ms, GC Workers: 8]
      [GC Worker Start (ms): Min: 120.5, Avg: 120.5, Max: 120.7, Diff: 0.2]
      [Ext Root Scanning (ms): Min: 0.4, Avg: 0.6, Max: 0.7, Diff: 0.2, Sum: 4.5]
      [Update RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
         [Processed Buffers: Min: 0, Avg: 0.0, Max: 0, Diff: 0, Sum: 0]
      [Scan RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
      [Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
      [Object Copy (ms): Min: 0.4, Avg: 0.4, Max: 0.4, Diff: 0.1, Sum: 3.0]
      [Termination (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.2]
         [Termination Attempts: Min: 3, Avg: 7.8, Max: 14, Diff: 11, Sum: 62]
      [GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.2]
      [GC Worker Total (ms): Min: 0.8, Avg: 1.0, Max: 1.1, Diff: 0.2, Sum: 7.9]
      [GC Worker End (ms): Min: 121.5, Avg: 121.5, Max: 121.5, Diff: 0.0]
   [Code Root Fixup: 0.0 ms]
   [Code Root Purge: 0.0 ms]
   [Clear CT: 0.1 ms]
   [Other: 0.4 ms]
      [Choose CSet: 0.0 ms]
      [Ref Proc: 0.1 ms]
      [Ref Enq: 0.0 ms]
      [Redirty Cards: 0.1 ms]
      [Humongous Register: 0.0 ms]
      [Humongous Reclaim: 0.0 ms]
      [Free CSet: 0.0 ms]
   [Eden: 2048.0K(4096.0K)->0.0B(2048.0K) Survivors: 0.0B->1024.0K Heap: 3946.2K(10.0M)->2816.1K(10.0M)]
 [Times: user=0.00 sys=0.00, real=0.00 secs] 
2020-03-26T10:10:49.682+0800: [GC concurrent-root-region-scan-start]
2020-03-26T10:10:49.682+0800: [GC pause (G1 Humongous Allocation) (young)2020-03-26T10:10:49.683+0800: [GC concurrent-root-region-scan-end, 0.0008804 secs]
2020-03-26T10:10:49.683+0800: [GC concurrent-mark-start]
, 0.0016254 secs]
   [Root Region Scan Waiting: 0.3 ms]
   [Parallel Time: 0.7 ms, GC Workers: 8]
      [GC Worker Start (ms): Min: 123.1, Avg: 123.3, Max: 123.7, Diff: 0.6]
      [Ext Root Scanning (ms): Min: 0.0, Avg: 0.1, Max: 0.3, Diff: 0.3, Sum: 0.9]
      [Update RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
         [Processed Buffers: Min: 0, Avg: 0.0, Max: 0, Diff: 0, Sum: 0]
      [Scan RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
      [Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
      [Object Copy (ms): Min: 0.0, Avg: 0.3, Max: 0.5, Diff: 0.5, Sum: 2.5]
      [Termination (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.2]
         [Termination Attempts: Min: 1, Avg: 3.4, Max: 7, Diff: 6, Sum: 27]
      [GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
      [GC Worker Total (ms): Min: 0.1, Avg: 0.5, Max: 0.7, Diff: 0.6, Sum: 3.7]
      [GC Worker End (ms): Min: 123.8, Avg: 123.8, Max: 123.8, Diff: 0.0]
   [Code Root Fixup: 0.0 ms]
   [Code Root Purge: 0.0 ms]
   [Clear CT: 0.1 ms]
   [Other: 0.4 ms]
      [Choose CSet: 0.0 ms]
      [Ref Proc: 0.1 ms]
      [Ref Enq: 0.0 ms]
      [Redirty Cards: 0.2 ms]
      [Humongous Register: 0.0 ms]
      [Humongous Reclaim: 0.0 ms]
      [Free CSet: 0.0 ms]
   [Eden: 1024.0K(2048.0K)->0.0B(1024.0K) Survivors: 1024.0K->1024.0K Heap: 3881.1K(10.0M)->3992.5K(10.0M)]
 [Times: user=0.00 sys=0.00, real=0.00 secs] 
2020-03-26T10:10:49.684+0800: [GC concurrent-mark-end, 0.0014553 secs]
2020-03-26T10:10:49.684+0800: [Full GC (Allocation Failure)  3992K->3722K(10M), 0.0028500 secs]
   [Eden: 0.0B(1024.0K)->0.0B(1024.0K) Survivors: 1024.0K->0.0B Heap: 3992.5K(10.0M)->3722.0K(10.0M)], [Metaspace: 3355K->3355K(1056768K)]
 [Times: user=0.11 sys=0.00, real=0.00 secs] 
2020-03-26T10:10:49.687+0800: [GC remark, 0.0000163 secs]
 [Times: user=0.00 sys=0.00, real=0.00 secs] 
2020-03-26T10:10:49.688+0800: [GC concurrent-mark-abort]
hello world
Heap
 garbage-first heap   total 10240K, used 4746K [0x00000000ff600000, 0x00000000ff700050, 0x0000000100000000)
  region size 1024K, 1 young (1024K), 0 survivors (0K)
 Metaspace       used 3448K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 376K, capacity 388K, committed 512K, reserved 1048576K

創建的字節數組爲大對象:2020-03-26T09:44:27.320+0800: [GC pause (G1 Humongous Allocation) (young) (initial-mark), 0.0048562 secs]

對應Young GC的五個步驟:

  • 根掃描:[Ext Root Scanning (ms): Min: 0.7, Avg: 2.1, Max: 3.2, Diff: 2.5, Sum: 17.1]
  • 更新RS:[Update RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
  • 處理RS:[Processed Buffers: Min: 0, Avg: 0.0, Max: 0, Diff: 0, Sum: 0]
  • 對象拷貝:[Object Copy (ms): Min: 0.0, Avg: 1.0, Max: 2.3, Diff: 2.3, Sum: 8.0]
  • 處理引用隊列:[Termination (ms): Min: 0.0, Avg: 0.4, Max: 0.7, Diff: 0.7, Sum: 3.1]

處理card table:[Clear CT: 0.1 ms]

回收集合:[Clear CT: 0.1 ms]

執行完Young GC後堆的使用情況:[Eden: 2048.0K(4096.0K)->0.0B(2048.0K) Survivors: 0.0B->1024.0K Heap: 3946.2K(10.0M)->2784.1K(10.0M)]

默認的region大小以及young region個數以及survivor region個數:region size 1024K, 1 young (1024K), 0 survivors (0K)——正好驗證了爲什麼創建的數組都是Humongous,因爲數組是連續佔用內存空間,大小肯定已經超過一個region大小的50%

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章