天天看點

深入了解JVM - 垃圾收集器

垃圾回收主要是要解決3件事情:

  1. 那些記憶體需要回收?
  2. 如何回收?
  3. 什麼時候回收?

術語解釋

并行/并發

  • 并行(Parallel):指多條垃圾收集線程并行工作,但此時使用者線程仍然處于等待狀态。
  • 并發(Concurrent):指使用者線程與垃圾收集線程同時執行(但不一定是并行的,可能會交替執行),使用者程式在繼續運作,而垃圾收集程式運作于另一個CPU上。

STW

STW(Stop the World) 是JVM等待所有的使用者線程進入safepoint并且阻塞,做一些全局性操作的行為,會造成全局停頓。通過參數-XX:+PrintSafepointStatistics和-XX:PrintSafepointStatisticsCount=1我們可以去檢視安全點日志:

vmop    [threads: total initially_running wait_to_block]
65968.203: ForceAsyncSafepoint [931   1   2]
[time: spin block sync cleanup vmop] page_trap_count
[2255  0  2255 11  0]  1      
  • spin:自旋階段,等待使用者線程進入安全點的時間。
  • block:讓使用者線程在安全點暫時阻塞的時間
  • sync:
  • cleanup:這個階段是JVM做的一些内部的清理工作的時間。
  • vmop(VM Operation):JVM執行的一些全局性工作,例如GC,代碼反優化。

那些記憶體需要回收

在強引用的情況下已經“死”了的對象就需要回收,在非強引用的情況下視情況回收。在java裡面,幾乎所有的對象執行個體都是在堆上配置設定,是以垃圾收集器第一件事情就是要判斷堆上的這些執行個體那些是“死去”的,那些還“活着”。判斷對象是否存活主要有兩種算法,一種是“引用計數算法”,一種是“可達性分析算法”。

“死去”的标準是:不可能再被任何途徑使用的對象。

引用計數算法

給對象中添加一個引用計數器,每當有一個地方引用它時,計數器值就加1;當引用失效時,計數器值就減1;任何時刻計數器為0的對象就是不可能再被使用的。

  • 優點:實作簡單, 判斷高效。
  • 缺點:當存在互相引用時,很難判斷對象是否已經“死了”。

可達性分析算法

通過一系列的稱為“GC Roots”的對象作為起始點,從這些節點開始向下搜尋,搜尋所走過的路徑稱為引用鍊(Reference Chain),當一個對象到GC Roots沒有任何引用鍊相連時,則證明此對象是不可用的。如圖所示,對象object 5、object 6、object 7雖然互相有關聯,但是它們到GC Roots是不可達的,是以它們将會被判定為是可回收的對象。

深入了解JVM - 垃圾收集器

GC Roots

在Java語言中,可作為GC Roots的對象包括下面幾種:

  • 方法區中類靜态屬性引用的對象;
  • 方法區中常量引用的對象;
  • 虛拟機棧(棧幀中的本地變量表)中引用的對象;
  • 本地方法棧中JNI(即一般說的Native方法)引用的對象;

對象引用類型

  • 強引用:強引用就是指在程式代碼之中普遍存在的,類似“Object obj = new Object()”這類的引用,隻要強引用還存在,垃圾收集器永遠不會回收掉被引用的對象。
  • 軟引用:表示對象還有用但并非必需,在系統将要發生記憶體溢出時回收。
  • 弱引用:也是表示對象還有用但并非必需,隻要發生GC,該對象就會被回收。
  • 虛引用:虛引用也稱為幽靈引用或者幻影引用。一個對象是否有虛引用的存在,完全不會對其生存時間構成影響,也無法通過虛引用來取得一個對象執行個體。
Java 4種引用的級别由高到低依次為:強引用 > 軟引用 > 弱引用 > 虛引用
引用類型 實作方式 被垃圾回收時間 用途 生存時間
強引用 Object obj = new Object() 從來不會 對象的一般狀态 JVM停止運作時終止
軟引用 SoftReference 出現OOM之前被回收 對象緩存 出現OOM之前
弱引用 WeakReference GC發生時 對象緩存 下一次GC之前
虛引用 PhantomReference GC發生時 在這個對象被收集器回收時收到一個系統通知 下一次GC之前

如何回收

垃圾收集算法

标記-清除算法

“标記-清除”(Mark-Sweep)算法:算法分為“标記”和“清除”兩個階段:

  • 标記:周遊所有的GC Roots,然後将所有GC Roots可達的對象标記為存活的對象。
  • 清除:周遊堆中所有的對象,将沒有标記的對象全部清除掉。
深入了解JVM - 垃圾收集器
  • 優點:高效(我個人覺得标記清除算法是所有垃圾收集算法中最高效的了,不知道為什麼大部分資料都說,這個算法效率低,如有知曉原因的請多多指教)
  • 缺點:清除之後會産生大量的記憶體碎片,空間碎片太多可能會導緻以後在程式運作過程中需要配置設定較大對象時,無法找到足夠的連續記憶體而不得不提前觸發另一次垃圾收集動作。

複制算法

将可用記憶體按容量劃分為大小相等的兩塊,每次隻使用其中的一塊。當這一塊的記憶體用完了,就将還存活着的對象複制到另外一塊上面,然後再把已使用過的記憶體空間一次清理掉,适用于年輕代。

深入了解JVM - 垃圾收集器
  • 優點:沒有了記憶體碎片
  • 缺點:浪費記憶體空間,需要有額外的空間進行配置設定擔保

複制算法适用于每次GC後存活對象很好的情況下,比如HotSpot虛拟機中的新生代,據統計新生代的對象存活率是2%。隻不過HotSpot虛拟機并不是将新生代直接對半劃分,而是分成了Eden和Survivor區,區域預設比值是​

​Eden:Survivor0:Survivor1=8:1:1​

​,這樣劃分後新生代浪費空間就隻有10%了。當Survivor空間不夠用時,需要依賴其他記憶體(這裡指老年代)進行配置設定擔保(Handle Promotion),配置設定擔保會将Minor GC後存活的對象直接放到老年代中。

标記-整理算法

首先标記出所有需要回收的對象,在标記完成後,後續步驟不是直接對可回收對象進行清理,而是讓所有存活的對象都向一端移動,然後直接清理掉端邊界以外的記憶體,适用于老年代。

深入了解JVM - 垃圾收集器
  • 優點:沒有了記憶體碎片,沒有浪費記憶體空間
  • 缺點:每次回收都需要移動對象,需要更新引用

分代收集算法

上面說的三種算法是垃圾回收的基礎算法,但是在虛拟機實作的過程中,不可能隻使用其中一種算法來完成垃圾收集,所有引入了分代收集的概念。它根據對象存活周期的不同将記憶體劃分為幾塊不同的區域, 如圖:

深入了解JVM - 垃圾收集器
在新生代中,因為每次Minor GC後,隻有少量存活,是以比較适合複制算法。而老年代中因為對象存活率高、沒有額外空間對它進行配置設定擔保,是以比較适合“标記—清理”或者“标記—整理”算法。

新生代對象首先在Eden進行配置設定,當Eden滿了過後觸發Minor GC,然後将存活的對象放到S0區域,再清空Eden區。當Eden再次滿了過後觸發Minor GC,然後将存活對象放到S1區域,再清空Eden和S0區,如此循環。當survivor區域不足以放下所有存活對象或者對象分代年齡達到臨界值時,會将對象放到老年代中。當老年代滿了後,會觸發Full GC。

HotSpot的算法實作

枚舉根節點

在垃圾收集過程中,枚舉根節點會導緻所有Java線程停頓(“Stop The World”)。為了能盡量減少對應用影響,我們需要盡量減少Java線程停頓時間。在前面我們列舉了那些對象是GC Roots,但是我們怎麼能快速找到這些GC Roots呢?因為我們越快找到這些對象,那麼Java線程停頓時間就越短。

現在主流Java虛拟機都是用的是​​準确式GC​​,是以Java線程停頓下來後,并不需要一個不漏地檢查完所有執行上下文和全局的引用位置。比如:虛拟機棧的本地變量表中,我們隻需要找到其中的引用對象就行了,而非引用對象是不會成為GC Roots的,如果我們每次GC都需要進行全棧掃描去查找GC Roots,那麼将增加Java線程的停頓時間。

在HotSpot中,它使用了一種OopMap的資料結構來存儲GC Roots的資訊,這樣,在枚舉根節點的時候,就可以避免全棧掃描了。但是什麼時候來記錄這些資訊呢?

安全點

HotSpot可以快速且準确地完成GC Roots枚舉,但是可能導緻引用關系變化,或者說OopMap内容變化的指令非常多,如果為每一條指令都生成對應的OopMap,那将會需要大量的額外空間,這樣GC的空間成本将會變得很高,是以就有了安全點(Safepoint)。程式隻有運作到了安全點才會暫停下來,然後将變化的引用資訊記錄到OopMap中。

在HotSpot中方法調用、循環跳轉、異常跳轉等功能才能産生安全點。

當GC發生的時候,需要讓所有的線程都到最近的安全點停下來。停頓方案有兩種:

  • 搶先式中斷:當GC發生的時,中斷所有線程,如果線程不在安全點就,恢複線程到安全點上,幾乎沒有虛拟機采用這種方式。
  • 主動式中斷:給線程設定一個中斷标志位,線程執行過程中會主動去輪詢這個标志位,當發現為中斷标志位是true時,挂起目前線程(輪詢标志的地方和安全點是重合的)。

安全區域

安全點可以解決正在執行中的線程到底安全點,記錄對象引用資訊。但是當線程處于Sleep或者Blocked狀态的時候,線程無法響應JVM中斷請求,是以安全點對這類線程就無效了,這時候就引入了安全區域(Safe Region)。

**安全區域是指在一段代碼片段之中,引用關系不會發生變化。在這個區域中的任意地方開始GC都是安全的。**我們也可以把Safe Region看做是被擴充了的Safepoint。

線上程執行到Safe Region中的代碼時,首先辨別自己已經進入了Safe Region,那樣,當在這段時間裡JVM要發起GC時,就不用管辨別自己為Safe Region狀态的線程了。線上程要離開Safe Region時,它要檢查系統是否已經完成了根節點枚舉(或者是整個GC過程),如果完成了,那線程就繼續執行,否則它就必須等待直到收到可以安全離開Safe Region的信号為止。

垃圾收集器

衡量垃圾收集器的三項最重要的名額是:記憶體占用(Footprint)、吞吐量(Throughput)和延遲(Latency),三者共同構成了一個“不可能三角”。

垃圾收集器就是記憶體回收的具體實作,主要有以下幾種,以及組合方式:

深入了解JVM - 垃圾收集器

Serial / Serial Old收集器

Serial是一個單線程的新生代收集器,采用複制算法。Serial Old是一個單線程的老年代收集器,采用标記-整理算法。

  • 優點:簡單高效,沒有線程切換帶來的開銷;
  • 缺點:進行垃圾收集時,必須暫停所有工作線程,直到完成,停頓時間長;多核情況下無法充分使用資源。
  • 使用場景:适合記憶體不大的情況;單核伺服器;

Serial/Serial Old收集器運作示意圖:

深入了解JVM - 垃圾收集器

ParNew收集器

ParNew收集器起始就是Serial收集器的多線程版,是一個新生代收集器,采用複制算法。

  • 優點:多核情況下,效率比Serial收集高
  • 缺點:進行垃圾收集時,必須暫停所有工作線程,直到完成,停頓時間長
  • 使用場景:多核伺服器

ParNew/Serial Old收集器運作示意圖:

深入了解JVM - 垃圾收集器
  • 并行(Parallel):指多條垃圾收集線程并行工作,但此時使用者線程仍然處于等待狀态。
  • 并發(Concurrent):指使用者線程與垃圾收集線程同時執行(但不一定是并行的,可能會交替執行),使用者程式在繼續運作,而垃圾收集程式運作于另一個CPU上。

Parallel Scavenge / Parallel Old收集器

Parallel Scavenge收集器是一個新生代的并行收集器,使用複制算法。Parallel Old收集器是一個老年代的并行收集器,使用标記-整理算法。

  • 優點:吞吐量高,多核情況下能充分利用資源
  • 缺點:進行垃圾收集時,必須暫停所有工作線程,直到完成,停頓時間長
  • 使用場景:追求高吞吐量的服務,如:批處理等背景任務
吞吐量 = 運作使用者代碼時間 /(運作使用者代碼時間 +垃圾收集時間)

Parallel Scavenge/Parallel Old收集器運作示意圖:

深入了解JVM - 垃圾收集器

CMS收集器

CMS(Concurrent Mark Sweep)收集器是一種以擷取最短回收停頓時間為目标的老年代收集器,使用标記-清除算法。

CMS 收集器運作示意圖:

深入了解JVM - 垃圾收集器

CMS 收集器主要包含4個階段:

  • 初始标記(CMS initial mark):僅僅隻是标記一下GC Roots能直接關聯到的對象,速度很快,需要停頓使用者線程。
  • 并發标記(CMS concurrent mark):就是進行GC Roots Tracing的過程,不需要停頓使用者線程。
  • 重新标記(CMS remark):修正并發标記期間因使用者程式繼續運作而導緻标記産生變動的那一部分對象的标記記錄,需要停頓使用者線程,停頓時間比初始标記稍長,比并發标記短很多。
  • 并發清除(CMS concurrent sweep):清除垃圾對象。
CMC停頓時間短的原因是:最耗時的并發标記和并發清除都是可以和使用者線程一起執行的。
  • 優點:并發收集、低停頓
  • 缺點:記憶體碎片多;導緻應用程式變慢;浮動垃圾問題;
  • 使用場景:适用于使用者互動類的服務
記憶體碎片問題

CMS使用的是标記—清除算法來實作的,是以就存在記憶體碎片的問題。當空間碎片過多,将會導緻無法配置設定大對象,這時不得不提前觸發一次Full GC。

  • -XX:+UseCMSCompactAtFullCollection:在CMS需要進行Full GC的時候,進行記憶體碎片的整理,這個過程無法并發(需要停頓使用者線程),預設是開啟。
  • -XX:CMSFullGCsBeforeCompaction:設定執行多好次Full GC後執行碎片整理,預設是0,表示每次都需要整理。
導緻應用程式變慢

CMS在并發标記和并發清除階段是和使用者線程一起運作的,這是垃圾回收機制就會占用部分線程(CPU資源)進行垃圾回收,線程數量預設為(CPU數量+3)/ 4,這樣就會導緻應用程式變慢。

浮動垃圾問題

在并發清除階段産生的垃圾,隻能在下一次GC的時候被回收,這部分垃圾稱為浮動垃圾(Floating Garbage)。CMS收集器因為無法處理浮動垃圾,可能會出現“Concurrent ModeFailure”失敗,而導緻臨時啟用Serial Old收集器來重新進行一次Full GC,這時停頓時間就很長了。是以CMS不能等到老年代滿了才進行回收,需要留一部分空間,提供給在并發收集過程中運作的線程使用。

  • -XX:CMSInitiatingOccupancyFraction:設定老年代預留白間比例,JDK1.5預設68%,JDK1.6以後預設是92%;如果這個值太高很容易導緻大量“Concurrent Mode Failure”失敗,性能反而降低。

G1收集器

G1(Garbage-First)是一款面向服務端應用的垃圾收集器。同時适用于新生代和老年代,與其他GC收集器相比,G1具備如下特點:

  • 并行與并發:G1 能充分利用多CPU、多核環境下的硬體優勢,使用多個CPU來縮短“Stop The World”停頓時間,部分其他收集器原本需要停頓Java線程執行的GC動作,G1收集器仍然可以通過并發的方式讓Java程式繼續執行。
  • 分代收集:分代概念在G1中依然得以保留。雖然G1可以不需要其他收集器配合就能獨立管理整個GC堆,但它能夠采用不同方式去處理新建立的對象和已存活一段時間、熬過多次GC的舊對象來擷取更好的收集效果。
  • 空間整合:G1從整體來看是基于标記-整理算法實作的收集器,從局部(兩個Region之間)上來看是基于複制算法實作的。這意味着G1運作期間不會産生記憶體空間碎片。此特性有利于程式長時間運作,配置設定大對象時不會因為無法找到連續記憶體空間而提前觸發下一次GC。
  • 可預測的停頓:這是G1相對CMS的一大優勢,G1除了降低停頓外,還能建立可預測的停頓時間模型,能讓使用者明确指定在一個長度為M毫秒的時間片段内,消耗在GC上的時間不得超過N毫秒,這幾乎已經是實時Java(RTSJ)的垃圾收集器的特征了。
G1堆模型
深入了解JVM - 垃圾收集器

它将整個Java堆劃分為多個大小相等的獨立區域(Region),雖然還保留有新生代和老年代的概念,但新生代和老年代不再是實體隔離的了,它們都是一部分Region(不需要連續)的集合。

Humongous區域:專門用來存儲大對象。G1認為隻要大小超過了一個Region容量一半的對象即可判定為大對象。

建立可靠的停頓預測模型

G1跟蹤各個Region裡面的垃圾堆積的價值大小(回收所獲得的空間大小以及回收所需時間的經驗值),在背景維護一個優先清單,每次根據允許的收集時間,優先回收價值最大的Region(這也就是Garbage-First名稱的來由)。這種使用Region劃分記憶體空間以及有優先級的區域回收方式,保證了G1收集器在有限的時間内可以擷取盡可能高的收集效率。

Remembered Set

記憶集(Remembered Set)用來記錄跨區域的對象引用(比如,新生代與老年代之間的對象引用)。記憶集其實是一種“抽象”的資料結構,“卡表”(Card Table)是記憶集的一種實作,邏輯圖如下:

深入了解JVM - 垃圾收集器

一個卡頁的記憶體中通常包含不止一個對象,隻要卡頁内有一個(或更多)對象的字段存在着跨代指針,那就将對應卡表的數組元素的值辨別為1,稱為這個元素變髒(Dirty),沒有則辨別為0。在垃圾收集發生時,隻要篩選出卡表中變髒的元素,就能輕易得出哪些卡頁記憶體塊中包含跨代指針,把它們加入GC Roots中一并掃描。

當有其他分代區域中對象引用了本區域對象時,在為屬性指派的那一刻,虛拟機通過寫屏障(Write Barrier)技術來維護記憶集的狀态,使用寫屏障後會帶來“僞共享”問題。

僞共享:CPU的緩存是以緩存行(cache line)為機關進行緩存的,當多個線程修改不同變量,而這些變量又處于同一個緩存行時就會影響彼此的性能。例如:線程1和線程2共享一個緩存行,線程1隻讀取緩存行中的變量1,線程2修改緩存行中的變量2,雖然線程1和線程2操作的是不同的變量,由于變量1和變量2同處于一個緩存行中,當變量2被修改後,緩存行失效,線程1要重新從主存中讀取,是以導緻緩存失效,進而産生性能問題。最簡單的解決辦法是填充,一個變量暫一個緩存行就不會有為共享了。

為了避免僞共享問題,可以不采用無條件的寫屏障,而是先檢查卡表标記,隻有當該卡表元素未被标記過時才将其标記為變髒。

G1中每個Region都有一個與之對應的Remembered Set,在做YGC的時候,隻需使用 年輕代中的region的Remembered Set作為根集,這些Remembered Set記錄了old->young的跨代引用,避免了掃描整堆。而Mixed GC的時候,old generation中記錄了old->old的Remembered Set,young->old的引用由掃描全部young generation region得到,這樣也不用掃描全部old generation region。

Young GC

標明所有年輕代裡的Region。通過控制年輕代的region個數,即年輕代記憶體大小,來控制young GC的時間開銷,複制回收算法。

回收前:

深入了解JVM - 垃圾收集器

回收後:

深入了解JVM - 垃圾收集器
Mixed GC

回收所有年輕代裡的Region,外加根據全局并發标記(global concurrent marking)統計得出收集收益高的若幹老年代Region,在使用者指定的開銷目标範圍内盡可能選擇收益高的老年代Region。Mixed GC不是full GC,它隻能回收部分老年代的Region,如果Mixed GC實在無法跟上程式配置設定記憶體的速度,導緻老年代填滿無法繼續進行Mixed GC,就會使用Serial old GC(Full GC)來收集整個GC Heap。

回收前:

深入了解JVM - 垃圾收集器

回收後:

深入了解JVM - 垃圾收集器
G1收集器的運作過程

G1收集器的運作大緻可劃分為以下幾個步驟:

深入了解JVM - 垃圾收集器
  • 初始标記(Initial Marking):完成标記GC ROOTS 直接可達的對象,需要停頓使用者線程(STW),耗時很短。
  • 并發标記(Concurrent Marking):從GC Root開始對堆中對象進行可達性分析,找出存活的對象,這階段耗時較長,可與使用者程式并發執行。當對象圖掃描完成以後,還要重新處理SATB記錄下的在并發時有引用變動的對象。
  • 最終标記(Final Marking):用于處理并發階段結束後仍遺留下來的最後那少量的SATB記錄,這階段需要停頓使用者線程(STW),但是可并行執行。
  • 篩選回收(Live Data Counting and Evacuation):對Region的回收價值和成本進行排序,根據使用者所期望的GC停頓時間來生成回收計劃,然後将存活對象放到空閑Region中,在清理掉所有的舊Region,這階段因為需要移動對象,是以會造成停頓使用者線程(STW)。

G1收集器要比其他的傳統垃圾收集器有着更高的記憶體占用負擔。根據經驗,G1至少要耗費大約相當于Java堆容量10%至20%的額外記憶體來維持收集器工作,原因如下:

  • 并發标記階段當使用者線程改變了對象引用關系時,G1使用了原始快照(SATB)算法來保證原本的對象關系不被打破。
  • G1也需要預留一部分記憶體,在垃圾收集過程中産生的新對象就需要在這部分預留記憶體裡配置設定,如果回收速度趕不上配置設定速度,也會發生Full GC。
  • 建立可靠的停頓預測模型也需要額外消耗記憶體空間
  • G1的Region有很多是以Remembered Set所需要的空間也更大
G1相關參數

1 GC相關的其他主要的參數有:

  • ​-XX:G1HeapRegionSize=n​

    ​​:設定Region大小,并非最終值,取值範圍從1M-32M之間,且是2的指數,預設為​

    ​size =(堆最小值+堆最大值)/ TARGET_REGION_NUMBER(2048) ,然後size取最靠近2的幂次數值, 并将size控制在[1M,32M]之間​

  • ​-XX:MaxGCPauseMillis​

    ​:設定G1收集過程目标時間,預設值200ms,不是硬性條件
  • ​-XX:G1NewSizePercent​

    ​:設定新生代最小值,預設值5%
  • ​-XX:G1MaxNewSizePercent​

    ​:設定新生代最大值,預設值60%
  • ​-XX:ParallelGCThreads​

    ​:設定STW期間,并行GC線程數
  • ​-XX:ConcGCThreads=n​

    ​:設定并發标記階段,并行執行的線程數
  • ​-XX:InitiatingHeapOccupancyPercent​

    ​:設定觸發标記周期的 Java 堆占用率門檻值。預設值是45%。這裡的java堆占比指的是non_young_capacity_bytes,包括old+humongous

Shenandoah垃圾收集器

Shenandoah是一款隻有OpenJDK才會包含的收集器,最開始由RedHat公司獨立發展後來貢獻給了OpenJDK,相比G1主要改進點在于:

  1. 支援并發的整理算法,Shenandoah的回收階段可以和使用者線程并發執行;
  2. Shenandoah 目前不使用分代收集,也就是沒有年輕代老年代的概念在裡面了;
  3. Shenandoah 摒棄了在G1中耗費大量記憶體和計算資源去維護的記憶集,改用名為“連接配接矩陣”(Connection Matrix)的全局資料結構來記錄跨Region的引用關系,降低了處理跨代指針時的記憶集維護消耗,也降低了僞共享問題的發生機率。

關鍵技術:

  • 連接配接矩陣
  • Brooks Pointer 轉發指針技術

詳情可以參考: ​​深入了解JVM - Shenandoah垃圾收集器​​

ZGC收集器

ZGC(Z Garbage Collector)是一款由Oracle公司研發的,以低延遲為首要目标的一款垃圾收集器。它是基于動态Region記憶體布局,(暫時)不設年齡分代,使用了讀屏障、染色指針和記憶體多重映射等技術來實作可并發的标記-整理算法的收集器。在JDK 11新加入,還在實驗階段,主要特點是:回收TB級記憶體(最大4T),停頓時間不超過10ms。

關鍵技術:

  1. 動态Region記憶體布局
  2. 染色指針(Colored Pointers)
  3. 讀屏障(Load Barrier)
  4. 記憶體多重映射

什麼時候回收

總的來說就是記憶體不足的時候進行垃圾回收。

Minor GC觸發條件

當Eden區滿時,且老年代的最大可用連續空間大于新生代所有對象的總和或者老年代最大連續空間比曆次晉升的平均值大,就進行Minor GC,否則FullGC。

Full GC觸發條件

  • 調用System.gc時,系統建議執行Full GC,但是不必然執行,不建議使用
  • 老年代空間不足
  • 方法區(永久代)空間不足
  • 通過Minor GC後進入老年代的平均大小大于老年代的可用記憶體
  • 由Eden區、From Space區向To Space區複制時,對象大小大于To Space可用記憶體,則把該對象轉存到老年代,且老年代的可用記憶體小于該對象大小

Mixed GC觸發條件(G1)

Mixed GC的觸發是由一些參數控制着:

  • G1HeapWastePercent參數:在global concurrent marking結束之後,我們可以知道old gen regions中有多少空間要被回收,在每次YGC之後和再次發生Mixed GC之前,會檢查垃圾占比是否達到此參數,隻有達到了,下次才會發生Mixed GC。
  • G1MixedGCLiveThresholdPercent:old generation region中的存活對象的占比,隻有在此參數之下,才會被選入CSet。
  • G1MixedGCCountTarget:一次global concurrent marking之後,最多執行Mixed GC的次數。
  • G1OldCSetRegionThresholdPercent:一次Mixed GC中能被選入CSet的最多old generation region數量。
堆記憶體任何部分來組成的回收集合(Collection Set,一般簡稱CSet)

總結

收集器

收集器 串行、并行or并發 新生代/老年代 算法 适用場景 SWT 使用限制
Serial 串行 新生代 複制算法 單CPU環境下的Client模式 整個GC過程都會STW 都可用
Serial Old 串行 老年代 标記-整理 單CPU環境下的Client模式、CMS的後備預案 整個GC過程都會STW 都可用
ParNew 并行 新生代 複制算法 多CPU環境時在Server模式下與CMS配合 整個GC過程都會STW JDK 1.3
Parallel Scavenge 并行 新生代 複制算法 在背景運算而不需要太多互動的任務 整個GC過程都會STW JDK 1.4
Parallel Old 并行 老年代 标記-整理 在背景運算而不需要太多互動的任務 整個GC過程都會STW JDK 1.4
CMS 并發 老年代 标記-清除 集中在網際網路站或B/S系統服務端上的Java應用 初始标記、重新标記會STW,如果記憶體碎片過多會引起Serial Old 的 FullGC,停頓時間很長 JDK 1.5
G1 并發 both 标記-整理+複制算法 面向服務端應用,将來替換CMS 初始标記、重新标記和篩選回收會STW JDK 1.7
Shenandoah 并發 無分代 并發整理+複制算法 低停頓應用 初始标記、最終标記、初始引用更新和最終引用更新會STW OpenJDK 12
ZGC 并發 無分代 并發整理+複制算法 低停頓,高吞吐量應用 初始标記、重新标記會STW JDK11和64位作業系統

垃圾回收器的重要參數

參數 描述
-XX:UseSerialGC 虛拟機運作在Client模式下的預設值,打開此開關後,使用Serial+Serial Old 的收集器組合進行記憶體回收
-XX:UseParNewGC 打開此開關後,使用 ParNew + Serial Old 的收集器組合進行記憶體回收
-XX:UseConcMarkSweepGC 打開此開關後,使用 ParNew + CMS + Serial Old 的收集器組合進行記憶體回收。Serial Old 收集器将作為 CMS 收集器出現 Concurrent Mode Failure 失敗後的後備收集器使用
-XX:UseParallelGC 虛拟機運作在 Server 模式下的預設值,打開此開關後,使用 Parallel Scavenge + Serial Old(PS MarkSweep) 的收集器組合進行記憶體回收
-XX:UseParallelOldGC 打開此開關後,使用 Parallel Scavenge + Parallel Old 的收集器組合進行記憶體回收
SurvivorRatio 新生代中 Eden 區域與 Survivor 區域的容量比值,預設為8,代表 Eden : Survivor0 : Survivor1 = 8 : 1:1
-XX:PretenureSizeThreshold 直接晉升到老年代的對象大小,設定這個參數後,大于這個參數的對象将直接在老年代配置設定
-XX:MaxTenuringThreshold 晉升到老年代的對象年齡,每個對象在堅持過一次 Minor GC 之後,年齡就增加1,當超過這個參數值時就進入老年代
-XX:UseAdaptiveSizePolicy 動态調整 Java 堆中各個區域的大小以及進入老年代的年齡
-XX:HandlePromotionFailure 是否允許配置設定擔保失敗,即老年代的剩餘空間不足以應付新生代的整個 Eden 和 Survivor 區的所有對象都存活的極端情況
-XX:ParallelGCThreads 設定并行GC時進行記憶體回收的線程數
-XX:GCTimeRatio GC 時間占總時間的比率,預設值為99,即允許 1% 的GC時間,僅在使用 Parallel Scavenge 收集器生效
-XX:MaxGCPauseMillis 設定 GC 的最大停頓時間,僅在使用 Parallel Scavenge 收集器時生效
-XX:CMSInitiatingOccupancyFraction 設定 CMS 收集器在老年代空間被使用多少後觸發垃圾收集,預設值為 68%,僅在使用 CMS 收集器時生效
-XX:UseCMSCompactAtFullCollection 設定 CMS 收集器在完成垃圾收集後是否要進行一次記憶體碎片整理,僅在使用 CMS 收集器時生效
-XX:CMSFullGCsBeforeCompaction 設定 CMS 收集器在進行若幹次垃圾收集後再啟動一次記憶體碎片整理,僅在使用 CMS 收集器時生效
-XX:+PrintGCDetails 虛拟機發生垃圾收集行為時列印記憶體回收日志,并且在程序退出的時候輸出目前的記憶體各區域配置設定情況

GC日志參數

參數 描述
-XX:+PrintGC 輸出GC垃圾回收日志
-verbose:gc 與-XX:+PrintGC相同
-XX:+PrintGCDetail 輸出詳細的GC垃圾回收日志
-XX:+PrintGCTimeStamps 輸出GC回收的時間戳
-XX:+PrintGCApplicationStoppedTIme 輸出GC垃圾回收時所占用的停頓時間
-XX:+PrintGCApplicationConcurrentTime 輸出GC并行回收時所占用的時間
-XX:+PrintHeapAtGC 輸出GC前後詳細的堆資訊
-Xloggc:filename 把GC日志輸出到filename指定的檔案
-XX:+PrintClassHistogram 輸出類資訊
-XX:+PrintTLAB 輸出TLAB空間使用情況
-XX:+PrintTenuringDistribution 輸出每次minor GC後新的存活對象的年齡門檻值
-XX:+PrintSafepointStatistics 輸出安全點日志
-XX:PrintSafepointStatisticsCount=1 輸出安全點日志的次數
-XX:+SafepointTimeout 開啟進入安全點逾時時輸出日志,逾時時間由SafepointTimeoutDelay指定
-XX:SafepointTimeoutDelay=2000 設定進去安全點的逾時時間
-XX:ErrorFile=/xxx/java_error.log JVM緻命錯誤日志

參考

繼續閱讀