Java 11 諸多新特性中,最重要的可能就是引入一個新的GC回收器:ZGC(The Z Garbage Collector)。這個GC,是Oracle爲了低延遲(暫停時間),大內存的場景而開發的一個新的垃圾回收器。java
在Java10 這幾年通過幾個版本的改進,調優已經接近極限。從時間上來看,Hotspot最近一次發佈新的GC,是在2006年發佈的G1。當時,AWS最大的實例只有1核CPU, 1.7G內存。而現在,AWS上已經能夠買到128核,3904G內存的服務器實例了。 因此,ZGC的設計目標就是爲了適用於這些場景:大內存低延遲(10ms之內)而且對應用的性能影響低(對應用吞吐量的影響在15%之內)。算法
之後也能夠在這個基礎之上,提供更多功能特性,如:多層heap結構(例如使用內存+固態磁盤組成一個混合堆區)服務器
在繼續介紹以前,有必要說明幾個GC中常見的基礎概念。多線程
GC對於性能,須要作出一些取捨。 例如:並行GC能夠利用多線程回收內存,但這也會帶來更多CPU線程的開銷(諸如上下文切換); 相似的,併發GC不會暫停應用的運行,可是這也會顯著的須要更多的內存,同時因爲GC線程和應用線程同時運行,這也增長了調度上的複雜度。併發
有了上述幾個基本概念,接下來,再來看看ZGC是如何工做的。 爲了達到ZGC的設計目標,其使用了Hotspot VM 的兩個新特性:app
這個技術就是,在指針上存放一些信息。(Java中的引用)ide
在64位平臺上(ZGC只支持64位),一個指針的尋址範圍會很是大,這樣,就能夠利用指針的某些位,來表示一些狀態位。 ZGC規定最大使用的堆,大小爲4TB,這樣的話,對於64位指針只須要使用42位就能夠尋址4TB的空間,那麼多餘出來的22位就能夠用於存放一些其餘狀態信息。 (指針長度一般是一個字(WORLD)的長度,而在64位平臺,一個字長64位)性能
目前,使用了22位中的4位長度,分別用來表示:是否已經finalize,重映射(remap),mark0,mark1。測試
指針着色,也會帶來一個問題。當解引用的時候,須要對其中的信息進行遮掩。(相似ip地址中的掩碼) 這個工做,在某些平臺是原生就支持的。如:SPARC; 而在x86平臺上則不支持。 爲了解決跨平臺的兼容性,ZGC團隊使用了multi-mapping技術來解決兼容問題。spa
要想理解multi-mapping,須要先來了解虛擬內存和物理內存的區別。
操做系統經過如下幾個技術來管理和維護虛擬內存和物理內存的映射關係:
multi-mapping就是把不一樣區域的虛擬內存映射到同一塊物理內存的技術。 經過remap, mark0, mark1 三個狀態字來控制映射過程。
圖示以下:
// // Page Allocation Tiers // --------------------- // // Page Type Page Size Object Size Limit Object Alignment // ------------------------------------------------------------------ // Small 2M <= 265K <MinObjAlignmentInBytes> // Medium 32M <= 4M 4K // Large X*M > 4M 2M // ------------------------------------------------------------------ // // // Address Space & Pointer Layout // ------------------------------ // // +--------------------------------+ 0x00007FFFFFFFFFFF (127TB) // . . // . . // . . // +--------------------------------+ 0x0000140000000000 (20TB) // | Remapped View | // +--------------------------------+ 0x0000100000000000 (16TB) // | (Reserved, but unused) | // +--------------------------------+ 0x00000c0000000000 (12TB) // | Marked1 View | // +--------------------------------+ 0x0000080000000000 (8TB) // | Marked0 View | // +--------------------------------+ 0x0000040000000000 (4TB) // . . // +--------------------------------+ 0x0000000000000000 // // // 6 4 4 4 4 4 0 // 3 7 6 5 2 1 0 // +-------------------+-+----+-----------------------------------------------+ // |00000000 00000000 0|0|1111|11 11111111 11111111 11111111 11111111 11111111| // +-------------------+-+----+-----------------------------------------------+ // | | | | // | | | * 41-0 Object Offset (42-bits, 4TB address space) // | | | // | | * 45-42 Metadata Bits (4-bits) 0001 = Marked0 (Address view 4-8TB) // | | 0010 = Marked1 (Address view 8-12TB) // | | 0100 = Remapped (Address view 16-20TB) // | | 1000 = Finalizable (Address view N/A) // | | // | * 46-46 Unused (1-bit, always zero) // | // * 63-47 Fixed (17-bits, always zero) //
在G1回收器中,使用寫屏障(write-barriers); 而ZGC中大量使用讀屏障(Load barriers),相似於:
void printName( Person person ) { String name = person.name; // 讀屏障,從堆中加載引用 System.out.println(name); // 不在使用讀屏障,直接使用,此時的name不在堆中加載(局部變量) }
在ZGC進行GC的處理週期中,第一步就是標記。這一步,就是爲了從堆中找出全部可到達的對象並在這些對象上打上標記。(也就是,找出全部不是垃圾的對象)
而ZGC的標記階段,又分爲三個步驟:
當標記階段完成,接下來就是重定位階段。重定位,就是把GC存活對象從新安放,從而釋放堆區空間。
那麼,爲何要移動這些對象,而不是簡單的直接填充而後直接釋放呢?
這個階段,ZGC會進行幾個步驟:
ZGC在實際中的性能還不能徹底說就必定好,不過從這個版原本看,幾回STW都是隻對ROOT對象集合,而且這個集合大小和堆大小無關。在標記階段最後一個STW,是某些條件下才出發的,而且是隻須要處理增量數據,而不是全量。並且,併發執行的標記階段也能夠在超過預期中的時間,也能夠返回,下一次再處理。
有關ZGC的性能,Stefan Karlsson 和 Per Liden 給出了一些數據,能夠在youtube找到。經過SPECjbb 2015的數據,能夠和Parallel GC大概有個比較,平均暫停時間基本在1~4ms。而G1 和 Parallel 則大體須要200ms級別。
不過,垃圾回收是一個十分複雜的工做。基準測試的數據不能徹底表明實際狀況。因此,ZGC的性能還須要進一步在實際線上系統中驗證。
指針着色和讀屏障爲之後提供了一些改進的可能:
隨着閃存以及一些非易失存儲(non-volatile memory)的發展,能夠在JVM中構建一個多層的堆結構。 指針着色中目前只是用了4位,還有不少的空間能夠存放一些元數據。這樣,之後就有可能在讀屏障時去其餘存儲中查找對象。
因爲讀取引用是能夠經過讀屏障來操做,那麼理論上就能夠在度屏障時,對數據進行壓縮或者解壓。
目前ZGC還處於一個實驗性的階段,還須要一段時間的驗證。G1從發佈到實際可用,也是通過了三年的時間。
服務器如今動輒上百G,甚至TB級別的內存,同時,Java對於堆的使用效率愈來愈重要。ZGC就是爲了超大堆、低延遲的場景而設計的。其經過指針着色,以及讀屏障等技術,使得ZGC爲Hotspot的GC提供了一些可能。