1. 程式人生 > >java虛擬機器面試乾貨【玖】_G1 GC的空間劃分

java虛擬機器面試乾貨【玖】_G1 GC的空間劃分

之前有看過介紹G1回收器的知識,但是一直沒有去完成一個整理。接著今天的功夫,好好把這部分知識總結一下。

什麼是G1 GC

通過前面的文章我們知道,在JDK 1.6版本之前,我們一般是使用ParNew+CMS兩個垃圾回收器來完成JVM中的垃圾回收的。但是它們的實現並不算特別的高效,詳情可見之前的文章總結(連結)。有鑑於此,JDK從1.7版本開始全新引入了一個新的垃圾回收期,就是今天要說的G1 GC。和之前的回收器相比,G1 GC有如下的特點:

並行性:G1在回收期間,可以有多個GC執行緒同時工作,可以有效利用多核的計算能力。

併發性:G1擁有與應用程式交替執行的能力,部分工作可以和應用程式同時執行,因此,一般來說,不會在整個回收階段發生完全阻塞應用程式的情況。

分代GC:G1依然是一個分代收集器,但是和之前的各類回收器不同,它同時兼顧了年輕代和老年代。對比其他回收器,它們或者工作在年輕代,或者工作在老年代。

空間整理:G1在回收過程中,會進行適當的物件移動,不像CMS那樣只是簡單地標記清理物件。在若干次GC後,CMS必須進行一次碎片整理。而G1不同,它每次回收都會有效地複製物件,減少空間碎片,進而提升內部迴圈速度。

可預見性:由於分割槽的原因,G1可以只選取部分割槽域進行記憶體回收,這樣縮小了回收的範圍,因此對於全域性停頓情況的發生也能得到較好的控制。

G1 GC的新特性

G1 GC依然採用的分代回收的演算法思想,在堆空間劃分上,依然保留了新生代(包括一個Eden區和兩個Survivor區)和老年代。但在設計層面和上一代回收器相比,有如下的改動:

1.去掉了永久代

我們知道,在HotSpot時代,我們將類載入器物件、類物件以及執行時常量池都儲存在永久代進行儲存。而且永久代的大小是固定的,當永久代空間不夠時會觸發Full GC,如果Full GC後空間仍無法滿足,則丟擲OOM:Perm Gen異常。而隨著JDK 8的到來,JVM不再有PermGen。類的元資料資訊(metadata)不再儲存在連續的堆空間上,而是移動到叫做元空間(Metaspace)的本地記憶體(Native memory,容量取決於是32/64位作業系統的可用虛擬記憶體大小)中。

對於元空間的大小我們可以通過置如下引數完成配置:

-XX:MetaspaceSize,初始空間大小,達到該值就會觸發垃圾收集進行型別解除安裝,同時GC會對該值進行調整:如果釋放了大量的空間,就適當降低該值;如果釋放了很少的空間,那麼在不超過MaxMetaspaceSize時,適當提高該值。


-XX:MaxMetaspaceSize來限制Metaspace空間的增長。預設情況下,-XX:MaxMetaspaceSize並沒有限制,如果沒有指定這個引數,元空間會在執行時根據需要動態調整。

-XX:MinMetaspaceFreeRatio,在GC之後,最小的Metaspace剩餘空間容量的百分比,減少為分配空間所導致的垃圾收集。

-XX:MaxMetaspaceFreeRatio,在GC之後,最大的Metaspace剩餘空間容量的百分比,減少為釋放空間所導致的垃圾收集。

2.增加了大物件區間

大物件(humongous)通常是很大的物件,佔據了超過Gl GC中區間(region)大小的50%。大物件沒有按照年輕代的回收方式把物件放入老年區,而是放置在老年區以外的大物件區間(humongous regions)裡面,即單獨分離出來形成一片新的區域,獨立管理。

當儲存一個比一個區間還大的大物件時,我們需要一個連續的可用分割槽集合進行儲存。橫跨的第一個區間叫大物件開始區間(humongous start),後面的區間叫連續的大物件區間(humongous continues)。

因為大物件在堆記憶體裡是物理連續的,所以存在兩個缺點:一是移動物件需要拷貝資料,大物件的拷貝效能開銷比較大;二是如果沒有足夠空間分配大物件時會觸發full GC,因此當系統中有越多越大的大物件儲存,回收可能越頻繁。

在JDK8u40之前的版本, 即便大物件區間是完全空閒的,也只會在並行回收迴圈的清除暫停階段才會回收大物件。而在 JDK8U40之後,新增了年輕代、Full GC階段針對大物件區間的回收功能,只要大物件區間不再包含任何引用,這些區間就會被回收並且放入空閒區間佇列。即年輕代回收、並行回收迴圈、 Full GC, 它們都會參與到大物件區間的回收工作。

G1 GC的區間

在上面介紹大物件區間時我們提到的區間(region)的概念,那麼區間是什麼呢?在G1 GC中,堆空間被平均分成若干個大小相等的區間,我們可以通過-XX:G1HeapRegionSize引數來設定一個區間的空間大小。區間的大小必須為2的倍數,範圍為1MB到32MB,即1MB、2MB、4MB、8MB、16MB或32MB。JVM最多可以容納大概2048個區間。

region支援動態分配,隨著每次GC的回收,一個region可以分配給eden、survivor、老年代、大物件區間、空閒區間等,不固定它的作用。說到這裡還要說下兩個概念,RSet和CSet:

RSet

每個區間都有一個關聯的Remembered Set(RSet),RSet的資料結構是hash表,裡面的資料是Card Table(堆中每512byte對映在card table 1byte)。說白了,RSet儲存的就是region中存活物件的指標。當region資料發生變化時,首先反應到card table中的一個或多個card上,RSet通過掃描內部的card table得知region中記憶體使用情況和存活物件,目的是讓區間可以獨立進行GC,而不用遍歷整個堆空間。RSet大小不能超過JVM大小的5%。

CSet

即Collection Sets,儲存一次GC中需要執行垃圾回收的區間(region),GC中CSet中的所有存活資料被轉移。CSet大小大概是JVM大小的1%。

下篇文章說說G1GC中的垃圾回收。