JAVA內存模型
JVM線程
- OSThread
JVM 對不同系統的線程抽象 - Thread
Thread 是 C++ 定義的線程基類,除了 OSThread 類,作爲其他線程的基類,它包含了 OSThread 對象的指針 - JavaThread:
Java 層創建的線程
CompilerThread :C1 CompilerThread 和C2 CompilerThread - VMThread:
單例原生線程VMThread
維護一個vm操作隊列(VMOperationQueue):GC,heapdump。
VM_CMS_Operation
VM_CGC_Operation
VM_GC_Operation - WorkerThread
子類GCTaskThread、GangWorker是用來執行gc任務的
Parallel:VMThread和GCTaskThread
CMS:VMThread和GangThread
ZGC:ZWorker和RuntimeWorker
JVM線程-Stop The Word
Stop the World 是JVM等待所有的用戶線程進入safepoint並且阻塞,做一些全局性操作的行爲。
- JIT相關
- Class redefinition
- 取消偏向鎖 ;
- Various debug operation
-XX:+PrintGCApplicationStoppedTime系統停頓時間
-XX:+PrintSafepointStatistics
-XX: PrintSafepointStatisticsCount=1
什麼是ZGC
A Scalable Low Latency Garbage Collector-可伸縮的低延遲垃圾收集器
目標:
1,TB級別內存(最多4TB)
2,最大10ms的暫停時間(STW)
3,最大15%的應用吞吐量減低
4,支持NUMA
特點
1,不支持分代,僅支持Linux 64位系統
2,設計了分頁管理支持TB級內存,
3,快速的進行併發標記和併發移動(Color Pointers)
4,設計了讀屏障,實現了增量標記
5,設計了物理內存和虛擬內存兩級內存管理
GC屏障 (GC Barriers)
Write Barrier:往堆寫入引用時,GC需要執行一些額外操作,對於在Java中:obj.field=xxx。常見於分代GC,比如老年代引用新生代,會維護card table。G1在維護Rset的時候使用了write barrier
Load barrier:在從堆讀取引用時,GC需要執行一些額外操作。對於在Java中,也就是像執行這樣的代碼Object xxx=obj.field時才需要額外操作。
ZGC的Load Barrier的作用:
運用在從堆裏面加載對象,而不是在後續訪問對象時。
判斷指針的當前顏色。如果是壞顏色,就修復它。
Heap Regions
堆的分區又被稱作ZPages
1,動態創建和銷燬,
2,動態大小,都是2M的整數倍(目前對x86_64)。
3,ZGC有3種不同的頁面類型:
小型 (2MB大小)
中型 (32MB大小)
大型 (2MB的倍數):大於4MB的一個對象。
4,區分物理內存和虛擬內存。
足夠的虛擬內存(ZGC總是4TB)
物理內存可以擴展到最大堆大小(使用-Xmx設置)
着色指針Colored Pointers
ZGC不支持compressed oops和32位系統平臺。因爲部分元數據存放在64指針之中
前42位爲定位對象地址的,緊接着4位分別爲finalizable , remapped , marked1和marked0 標誌位。更高的位目前都是0。
通過marked1和marked0判斷是否被標記
通過remapped知道沒有指向重定位後的集合中
通過finalizable判斷是否只能通過Finalizer才能訪問到。
讀屏障可以實現對象的##### ZGC過程-標記階段
-
標記階段
Live Map是一個位圖(bitmap) ,用於存儲給定索引處的對象是否可達和/或最終可達。
ZGC把內存分爲多個Striped,Striped在多個線程上進行分配,每個線程負責各自的Striped,當一個線程完成時,也會處理其他現在未完成的Striped
-
標記階段-初始標記
第一階段是STW,其中GC roots被標記爲活對象。
如果一個對象不能通過遍歷從roots開始的對象圖來訪問,那麼應用程序也就無法訪問它,則該對象被認爲是垃圾。從roots訪問的對象集合稱爲Live集(live-set)。GC roots標記步驟非常短,因爲roots的總數通常比較小。
-
標記階段-併發標記
初始標記階段完成後,應用程序恢復執行,ZGC開始下一階段.
該階段同時遍歷對象圖並標記所有可訪問的對象。
應用程序線程中的load-barrier讀屏障針使用掩碼測試所有已加載的引用,該掩碼確定它們是否已標記或尚未標記,將未標記的引用推送到線程局部標記緩衝區。只要此緩衝區已滿,GC線程就可以獲得此緩衝區的所有權,並以遞歸方式遍歷此緩衝區中的所有可到達對象。在應用程序線程中標記只是將引用推送到緩衝區,GC線程負責遍歷對象圖並更新Live map. -
標記階段-標記結束
在遍歷完成之後,有一個最終的,時間很短的的Stop The World階段,清空緩存區,解決併發階段引用變化的情況。
ZGC過程-重定位階段
重定位階段-選擇重定位pages
重定位前,此時需要解決的問題是要重定位哪些pages,稱爲Relocation Set。
生成了forwarding tables記錄指針變化。
- 定位階段-重定位roots
選擇重定位集後,會出現一個Stop The World暫停,其中ZGC重定位該集合中root對象,並將他們的引用映射到新位置。
遍歷所有的roots,並進行遷移,指針記錄到forwarding tables
(1)重定位階段-重定位roots指向的對象
移動root後,下一階段是併發重定位。
在此階段,GC線程遍歷重定位集並重新定位其包含的頁中所有對象。
如果應用程序線程試圖在GC重新定位對象之前加載它們,那麼應用程序線程也可以重定位該對象,這可以通過讀屏障(在從堆加載引用時觸發)實現。
一旦一個page已經沒有元素了,此時它就可以馬上被重新使用。不用等到GC所有階段結束。
(2)重定位階段-重定位root指向的對象
同時注意,此時其他指向5的對象指針仍然沒有變,當這些對象需要訪問5時如果不做特殊處理就會出錯,這就是load barrier的作用,它會讓當前對象對5的引用重新指向新的地址。
重定位階段結束,同時也意味着到此時,GC週期結束。但是就會發現上面還有部分指針沒有remap到正確的位置。如果需要進行修復,那也要進行一次全部遍歷。如下圖:進行一次併發重映射。
但是就會發現,第一個併發標記和最後的併發重映射都是一個對象遍歷,所有這兩個階段可以合併。合併就設計到一個問題,怎麼判斷當前的引用是上次GC循環沒有remap的呢?這就需要一個標誌,這也就是引用標誌中有兩位特殊的標誌位marked1和marked2。
JDK 11中ZGC的不足
1,實驗性質
2,ZGC僅實現了單代內存管理,也就是說沒有考慮熱點數據與冷數據,這個在商業的C4已經支持。
3,C2的支持還不夠完善;
4,不支持Graal, HDSB等功能
5,ZGC並不是下一代GC唯一的發展方向:Shenandoah
http://ju.outofmemory.cn/entry/367911
https://blog.csdn.net/qq_42882671/article/details/82622328
https://blog.longyb.com/2018/10/04/Zgc_Introduction/
https://dinfuehr.github.io/blog/a-first-look-into-zgc/
過程概覽地址:http://hg.openjdk.java.net/zgc/zgc/file/59c07aef65ac/src/hotspot/share/gc/z/zDriver.cpp#l316