垃圾回收器的發展歷程
背景
01、G1
解決的問題
G1
垃圾回收器是04
年正式提出,12
開始正式支持,在17
年作為JDK9
默認的垃圾處理器。
在04
年的時候,java
程序堆的內存越來越大,從而導致程序中可存活的活對象越來越多,因此GC
的STW
時間越來越長。這是G1
要解決的主要問題:STW
帶來的停頓時間太長了。
CMS
在此之前效率也很高,但活對象數量一多,STW
時間也很長。而且CMS
無法解決內存碎片化的問題。
G1
還解決的問題是:CMS
在GC
后,無法compact
內存。
02、G1
達成的目標
(1)減少由於STW
而帶來的程序延遲時間,做到偽實時、低延時、可設定目標;
可設定目標是指能夠設置GC
最大STW
停頓的時間,G1
會盡量達成目的,但不一定達成。
-XX:MaxGCPauseMillis=N
默認情況下是250毫秒
(2)解決CMS
在GC
后,無法壓縮程序內存的問題;
(3)在JDK9
之后,默認的垃圾處理器就是G1
;它適用於堆內存較大的情況下(>4~6G
);
G1
垃圾回收器
一、G1
內存布局
G1
不再遵循之前的堆中對象的分代排列,而是將堆分成若干個等大的區域。
而是變成:
默認是分成
2048
個區域,-XX:G1HeapRegionSize=N 2048
Humongous
:當你分配的一個對象超過一半區域的大小時,這個對象就會被放入這個區域。這個區域屬於老年代區域。
二、G1
的介紹
G1
垃圾回收器不再回收整個堆,而是選擇一個Collection Set
(CS
)。而且每次GC
時,會估計每個Region
中的垃圾比例,優先回收垃圾多的Region
。這就為什么被叫做Garbage First
算法。這也是為什么G1
可以控制STW
停頓時間的原因。
G1
含有三種GC
算法:
Full young GC
:年輕代GC
算法:STW
、Parallel
、Copying
- 老年代
GC
算法:Mostly-concurrent marking
、Incremental compaction
Mixed GC
:混合GC
三、G1
引來的問題
問題描述
G1
將年輕代、老年代區域划分為許多個小區域,增加在GC
判斷對象是否為垃圾的難度。比如:
- 老年代對象可能持有年代代的引用(跨代引用)
- 不同的
Region
間的互相引用
假設在Full young GC
時,某個年輕代Region
對象可能被老年代的某個對象引用,那么我在回收這個年輕代Region
時,怎么知道這里面的對象是否被其他Region
、老年代引用呢?
問題解決
Remembered Set
、Card Table
1、CardTable
每個Region
中分為很多區域,每個區域我們成為CardTable
,對應的就是上述藍色區域;每個CardTable
有多個entry
組成。當對應的內存空間發生改變時,就會標記為dirty
。
2、RememberedSet
當Region1
的CardTable
引用Region2
的CardTable
時,Region2
的RememberedSet
就會記錄對應CardTable
中的entry
,可以根據其找到對應的內存區域。
3、解析
當某個內存對應進行賦值是,就是對象的set
方法,我們可以在這種方法上添加dirty
的描述。
這其實就是典型的時間換空間的做法:用額外的空間維護引用信息,這就是占用5~10%
的過多內存占用。
解決方法的實現
1、Write Barrier
介紹
Write barrier
是一種向JVM
注入的一小段代碼,用於記錄指針變化。比如說object.field = <reference>
。
在JVM
開始更新指針時,就經過以下幾步:
- 標記
Card
為Dirty
- 將
Card
存入Dirty Card Queue
隊列中
這里有一個問題:為什么要放在隊列里,而不是直接去更新RememberedSet
呢?
這是因為JVM
運行可能會有多個線程並行的修改RememberedSet
,這樣就需要花費額外的時間來解決多線程同步問題。而這種更新引用是頻繁的,所以這種額外時間是無法忍受的。
2、Dirty Card Queue
這個隊列有白、綠、黃、紅四個顏色,表示應用線程往這個隊列放任務的狀態。
-
White
表示沒有應用線程往隊列里放任務,什么事都不用干。 -
Green
此時Refinement
線程開始被激活,開始更新RS
。-XX:G1ConcRefinementGreenZone=N
-
Yellow
此時全部的Refinement
線程都被激活,來更新RS
。-XX:G1ConcRefinementYellowZone=N
-
Red
這個時候,應用線程也開始參與排空隊列的工作。-XX:G1ConcRefinementRedZone=N
四、GC
算法的過程
1、Fully young GC
GC
的過程
(1)STW
此時會暫停所有堆中的對象,將部分Region
拷貝到指定區域。
(2)構建Collection Set
fully young GC
就是選取所有的Eden
和Survivor
。
(3)掃描GC Roots
(4)更新RememberedSet
排空Dirty Card Queue
(5)Process RS
根據RS
找到要GC
的對象被哪些對象引用了。
(6)對象拷貝
survivor
區域對象的調整。
(7)Reference Processing
額外會做的事
G1
記錄每個階段的時間,用於后期自動調優。比如說會記錄Eden
、Survivor
的數量和GC
時間,后期會根據我們之前設定的暫停目標來自動調整Region
數量。
但是我們設置暫停目標越短,年輕代的Region
數量就越少。但這可能會導致Fully young GC
頻繁發生。
2、Old GC
當堆用量達到一定程度時,就會觸發old GC
。可以通過以下參數進行設置:
-XX:InitatingHeapOccpancyPercent=45
old GC
有一個很大特點就是並發進行的。但它是如何在堆中不斷變化的情況下,確定哪些是要清理的垃圾對象呢?
三色標記算法
這種算法實現了在不暫停應用線程的情況下進行並發標記,標記過程過如下:
(1)將GC Root
對象記錄為黑色,其直接引用對象記錄為灰色,並將這些灰色對象放入一個隊列中
(2)從隊列取出對象,將其標為黑色,將其引用對象記錄為灰色,再放入隊列中
(3)直到隊列中無對象為止
三色標記算法的缺點:Lost Object Problem
三色標記算法並沒有完全將所有的活對象都標記出來,這就是Lost Object Problem
問題。比如說:
(1)剛開始時
(2)在即將描述將C
標為灰色的一剎那
此時,C
依然是活對象,但是已經無法將其標記了。
(3)結果
Lost Object Problem
的解決
這種解決辦法還是通過Write barrier
技術來解決。當B.c=null
,也就是C
指針被刪除時,G1
還是被認為活對象。
那如果
C
是新生對象呢?這是老年代GC
Old GC
過程
(1)STW
老年代GC
會在這個時候,進行一次Fully young GC
(2)恢復應用線程
(3)使用三色標記算法並發標記(init marking
)
(4)STW
這時候會有一個Remark
階段,主要是解決SATB
、Reference processing
還會有一個Cleanup
階段,用於回收全為空的區
(5)恢復應用線程
3、Mixed GC
我們直到CMS
最大的缺點就是無法進行壓縮操作,而G1
就通過Mixed GC
解決了這個問題。
Mixed GC
沒有固定觸發條件,他是根據Fully young GC
收集的信息和我們配置的時間來決定,是否觸發Mixed GC
。它會根據暫停目標,來優先選擇垃圾最多的Old Region
來執行。
Mixed GC
會選擇若干個Region
進行,默認是選擇1/8
的Old Region
、Eden Region
、Survivor Region
。
Mixed GC
的過程跟Fully young GC
的過程相同,都是:STW
、Parallel
、Copying
。