JVM-新一代GC之低延遲垃圾收集器

低延遲垃圾收集器

Shenandoah和ZGC爲何被稱爲低延遲GC,由於它幾乎整個工做過程所有都是併發的,只有初始標記、最終標記這些階段有短暫的停頓,這部分停頓的時間基本上是固定的,與堆的容量、堆中對象的數量沒有正比例關係。實際上,它們均可以在任意可管理的(譬如如今ZGC只能管理4TB之內的堆)堆容量下,實現垃圾收集的停頓都不超過十毫秒這種之前聽起來是天方夜譚、匪夷所思的目標。這兩款目前仍處於實驗狀態的收集器,被官方命名爲「低延遲垃圾收集器」。java

衡量垃圾收集器的三項最重要的指標是:內存佔用(Footprint)、吞吐量(Throughput)和延遲(Latency),三者共同構成了一個「不可能三角」。git

1. Shenandoah垃圾回收器

比起稍後要介紹的有着Oracle正朔血統的ZGC,Shenandoah反而更像是G1的下一代繼承者。使用轉發指針(Forwarding Pointer,也常被稱爲Indirection Pointer)來實現對象移動與用戶程序併發的一種解決方案。github

那Shenandoah相比起G1又有什麼改進呢?

雖然Shenandoah也是使用基於Region的堆內存佈局,一樣有着用於存放大對象的HumongousRegion,默認的回收策略也一樣是優先處理回收價值最大的Region……但在管理堆內存方面,它與G1至少有三個明顯的不一樣之處,最重要的固然是支持併發的整理算法,G1的回收階段是能夠多線程並行的,但卻不能與用戶線程併發,這點做爲Shenandoah最核心的功能稍後筆者會着重講解。其次,Shenandoah(目前)是默認不使用分代收集的,換言之,不會有專門的新生代Region或者老年代Region的存在,沒有實現分代,並非說分代對Shenandoah沒有價值,這更可能是出於性價比的權衡,基於工做量上的考慮而將其放到優先級較低的位置上。最後,Shenandoah摒棄了在G1中耗費大量內存和計算資源去維護的記憶集,改用名爲「鏈接矩陣」(Connection Matrix)的全局數據結構來記錄跨Region的引用關係,下降了處理跨代指針時的記憶集維護消耗,也下降了僞共享問題。算法

Shenandoah收集器的工做過程大體能夠劃分爲如下九個階段
  1. 初始標記 這個階段還是「Stop The World」的,但停頓時間與堆大小無關,只與GC Roots的數量相關數據結構

  2. 併發標記 與G1同樣,遍歷對象圖,標記出所有可達的對象,這個階段是與用戶線程一塊兒併發的,時間長短取決於堆中存活對象的數量以及對象圖的結構複雜程度。多線程

  3. 最終標記 與G1同樣,處理剩餘的SATB掃描,並在這個階段統計出回收價值最高的Region,將這些Region構成一組回收集(Collection Set)。最終標記階段也會有一小段短暫的停頓。架構

  4. 併發清理 這個階段用於清理那些整個區域內連一個存活對象都沒有找到的Region併發

  5. 併發回收 在這個階段,Shenandoah要把回收集裏面的存活對象先複製一份到其餘未被使用的Region之中。複製對象這件事情若是將用戶線程凍結起來再作那是至關簡單的,但若是二者必需要同時併發進行的話,就變得複雜起來了。其困難點是在移動對象的同時,用戶線程仍然可能不停對被移動的對象進行讀寫訪問,移動對象是一次性的行爲,但移動以後整個內存中全部指向該對象的引用都仍是舊對象的地址,這是很難一瞬間所有改變過來的。對於併發回收階段遇到的這些困難,Shenandoah將會經過讀屏障和被稱爲「Brooks Pointers」的轉發指針來解決(講解完Shenandoah整個工做過程以後筆者還要再回頭介紹它)。併發回收階段運行的時間長短取決於回收集的大小佈局

  6. 初始引用更新 併發回收階段複製對象結束後,還須要把堆中全部指向舊對象的引用修正到複製後的新地址,這個操做稱爲引用更新。性能

  7. 併發引用更新 真正開始進行引用更新操做,這個階段是與用戶線程一塊兒併發的,時間長短取決於內存中涉及的引用數量的多少。併發引用更新與併發標記不一樣,它再也不須要沿着對象圖來搜索,只須要按照內存物理地址的順序,線性地搜索出引用類型,把舊值改成新值便可。

  8. 最終引用更新 解決了堆中的引用更新後,還要修正存在於GC Roots中的引用。這個階段是Shenandoah的最後一次停頓,停頓時間只與GC Roots的數量相關。

  9. 併發清理 通過併發回收和引用更新以後,整個回收集中全部的Region已再無存活對象,最後再調用一次併發清理過程來回收這些Region的內存空間,供之後新對象分配使用。

Shenandoah收集器性能

2016年作該測試時的Shenandoah並無徹底達成預約目標,停頓時間比其餘幾款收集器確實有了質的飛躍,但也並未實現最大停頓時間控制在十毫秒之內的目標,而吞吐量方面則出現了很明顯的降低。

Shenandoah的性能在日益改善,逐步接近「Low-Pause」的目標。此外,RedHat也積極拓展Shenandoah的使用範圍,將其Backport到JDK 11甚至是JDK 8之上,讓更多不方便升級JDK版本的應用也可以享受到垃圾收集器技術發展的最前沿成果。

2. ZGC收集器

ZGC是一款在JDK 11中新加入的具備實驗性質[插圖]的低延遲垃圾收集器,是由Oracle公司研發的。2018年Oracle建立了JEP 333將ZGC提交給OpenJDK,推進其進入OpenJDK 11的發佈清單之中。

ZGC和Shenandoah的目標是高度類似的,都但願在儘量對吞吐量影響不太大的前提下,實如今任意堆內存大小下均可以把垃圾收集的停頓時間限制在十毫秒之內的低延遲。可是ZGC和Shenandoah的實現思路又是差別顯著的。

ZGC收集器是一款基於Region內存佈局的,(暫時)不設分代的,使用了讀屏障、染色指針和內存多重映射等技術來實現可併發的標記-整理算法的,以低延遲爲首要目標的一款垃圾收集器。

併發整理算法的實現

Shenandoah使用轉發指針和讀屏障來實現併發整理,ZGC雖然一樣用到了讀屏障,但用的倒是一條與Shenandoah徹底不一樣,更加複雜精巧的解題思路。

ZGC收集器有一個標誌性的設計是它採用的染色指針技術(Colored Pointer),直接把標記信息記在引用對象的指針上。指針對於計算機來說,它也是一個信息的載體,可是目前而言,內存中的理論可訪問信息是遠大於實際需求的,儘管Linux高18位不能用來尋址,但剩餘的46位也足以知足需求,因此ZGC團隊就將指針信息載體進行染色,將其高4位用來存儲四個記號信息,經過這些標誌位,虛擬機能夠直接從指針中看到其引用對象的三色標記狀態、是否進入了重分配集(即被移動過)、是否只能經過finalize()方法才能被訪問到。

因爲這些標誌位進一步壓縮了本來就只有46位的地址空間,也直接致使ZGC可以管理的內存不能夠超過4TB(2的42次冪)。

染色指針的三大優點:
  1. 染色指針可使得一旦某個Region的存活對象被移走以後,這個Region當即就可以被釋放和重用掉,而沒必要等待整個堆中全部指向該Region的引用都被修正後才能清理。

  2. 染色指針能夠大幅減小在垃圾收集過程當中內存屏障的使用數量,設置內存屏障,尤爲是寫屏障的目的一般是爲了記錄對象引用的變更狀況,若是將這些信息直接維護在指針中,顯然就能夠省去一些專門的記錄操做。

  3. 染色指針能夠做爲一種可擴展的存儲結構用來記錄更多與對象標記、重定位過程相關的數據,以便往後進一步提升性能。

Java虛擬機做爲一個普普統統的進程,這樣隨意從新定義內存中某些指針的其中幾位,操做系統是否支持?處理器是否支持?

這裏面的解決方案要涉及虛擬內存映射技術。把染色指針中的標誌位看做是地址的分段符,那隻要將這些不一樣的地址段都映射到同一個物理內存空間,通過多重映射轉換後,就可使用染色指針正常進行尋址了。

image

ZGC的運做過程大體可劃分爲如下四個大的階段
  1. 併發標記(Concurrent Mark):併發標記是遍歷對象圖作可達性分析的階段,與G一、Shenandoah不一樣的是,ZGC的標記是在指針上而不是在對象上進行的,標記階段會更新染色指針中的Marked 0、Marked 1標誌位。

  2. 併發預備重分配(Concurrent Prepare for Relocate):這個階段須要根據特定的查詢條件統計得出本次收集過程要清理哪些Region,ZGC劃分Region的目的並不是爲了像G1那樣作收益優先的增量回收,而實用範圍更大的掃描成本換取省去G1中記憶集的維護成本。此外,在JDK12的ZGC中開始支持的類卸載以及弱引用的處理,也是在這個階段中完成的。

  3. 併發重分配(Concurrent Relocate):重分配是ZGC執行過程當中的核心階段,這個過程要把重分配集中的存活對象複製到新的Region上,併爲重分配集中的每一個Region維護一個轉發表(Forward Table),記錄從舊對象到新對象的轉向關係。得益於染色指針的支持,ZGC收集器能僅從引用上就明確得知一個對象是否處於重分配集之中,若是用戶線程此時併發訪問了位於重分配集中的對象,此次訪問將會被預置的內存屏障所截獲,而後當即根據Region上的轉發表記錄將訪問轉發到新複製的對象上,並同時修正更新該引用的值,使其直接指向新對象,ZGC將這種行爲稱爲指針的「自愈」(Self-Healing)能力。

  4. 併發重映射(Concurrent Remap):重映射所作的就是修正整個堆中指向重分配集中舊對象的全部引用。ZGC的併發重映射並非一個必需要「迫切」去完成的任務,由於前面提到ZGC有"自愈"能力,最壞也就多跳轉一層,這時候,一旦全部指針都被修正以後,原來記錄新舊對象關係的轉發表就能夠釋放掉了。

ZGC收集器性能
  1. ZGC的設計理念是迄今垃圾收集器研究的最前沿成果,但是,一定要有優有劣纔會稱做權衡,ZGC的這種選擇[插圖]也限制了它能承受的對象分配速率不會過高,目前惟一的辦法就是儘量地增長堆容量大小,得到更多喘息的時間。

  2. ZGC還有一個常在技術資料上被說起的優勢是支持「NUMA-Aware」非統一內存訪問架構的內存分配。因爲摩爾定律逐漸失效,現代處理器因頻率發展受限轉而向多核方向發展,這就形成了若是要訪問被其餘處理器核心管理的內存,就必須經過Inter-Connect通道來完成,這要比訪問處理器的本地內存慢得多,ZGC收集器會優先嚐試在請求線程當前所處的處理器的本地內存上分配對象,以保證高效內存訪問。

  3. 在ZGC的「弱項」吞吐量方面,以低延遲爲首要目標的ZGC已經達到了以高吞吐量爲目標Parallel Scavenge的99%,直接超越了G1。

  4. ZGC均能絕不費勁地控制在十毫秒以內

image

image

聲明:本問參考書籍《深刻理解Java虛擬機:JVM高級特性與最佳實踐(第3版) 》,這個版本剛上市兩個月,新增了一些新的GC和內存分配策略的知識,大夥有興趣能夠看看。如有侵權,請聯繫刪除,謝謝!



若是你喜歡個人文章,那麻煩請關注個人公衆號,該公衆號還處於初始階段,謝謝你們的支持。

關注公衆號,回覆 java架構獲取架構視頻資源(後期還會分享不一樣的優質資源噢)。回覆 找對象能夠拉你進IT單身交友羣噢。

想看往期文章, 請點擊個人GitHub地址: github.com/fantj2016/j…

相關文章
相關標籤/搜索