根節點枚舉必須在一個能保證一致性快照的時間進行----「一致性」就比如整個系統暫停,不會出如今分析過程當中,整個飲用鏈還在不斷變化的狀況,若是不正保證的話,那麼分析結果的準確性也沒法保證。CMS,G1,ZGC等收集器也會發生STW。c++
其實,在用戶線程停頓下來以後,其實並不須要掃描整個堆,虛擬機有辦法直接獲得對象引用信息。HotSpot使用OopMap這樣的數據結構來解決。數組
在類加載完成的時候,虛擬機就會把對象內什麼偏移量是什麼類型信息計算出來。安全
那麼在JVM運行過程當中是如何處理的呢:安全點微信
致使OopMap變化的因素有不少,虛擬機不可能爲全部的操做都生成對應的OopMap,操做代價和空間成本過於高昂。在特定的位置記錄了這些信息,這些位置稱爲安全點。markdown
安全點須要考慮的問題:數據結構
不能太少讓GC收集器等待時間過長,不能太頻繁增大內存負荷oop
因此選取的根據是能讓線程長時間執行,如方法調用,循環等post
如何讓線程跑到最近的安全點:搶佔式中斷和主動式中斷spa
搶佔式:先把全部用戶線程中斷,若是不在安全點,則恢復執行到安全點線程
主動式:設置一個標識位,讓線程執行時不斷去輪詢這個標誌位
彙編級別如何實現的:HotSpot使用內存保護陷阱的方式把輪詢操做精簡到只有一個彙編指令(test)的程度。在執行到test指令時,在預先註冊的異常處理器中掛起線程實現等待。
那些已經sleep和block的線程如何處理:安全區域
安全區域指在某一個代碼片斷中,引用關係不會發生變化。
線程會標識本身進入了安全區域,當線程要離開安全區域時,要檢查虛擬機是否完成了根節點枚舉,完成則繼續執行線程,未完成繼續等待,直到收到能夠離開的信號。
若是新生代中含有老年代對象的指針,不可能把老年代做爲GC Roots掃描範圍,那麼該如何解決:記憶集與卡表
爲解決對象跨代引用所帶來的問題,垃圾收集器在新生代中創建了名爲記憶集(Remembered Set)的數據結構,用以免把整個老年代加進GC Roots掃描範圍。
HotSpot對記憶集的實現即爲卡表。卡表選擇較爲粗獷的精度來節省成本,它的每個記錄對應一塊內存區域,該區域內有跨代指針。
卡表最簡單的形式能夠只是一個字節數組,字節數組的每個元素都對應着其標識的內存區域中一塊特定大小的內存塊,這個內存塊被稱做「卡頁」(Card Page)。
一個卡頁的內存中一般包含不止一個對象,只要卡頁內有一個(或更多)對象的字段存在着跨代指針,那就將對應卡表的數組元素的值標識爲1,稱爲這個元素變髒(Dirty),沒有則標識爲0。在垃圾收集發生時,只要篩選出卡表中變髒的元素,就能輕易得出哪些卡頁內存塊中包含跨代指針,把它們加入GC Roots中一併掃描。
解決了如何使用記憶集來縮減GC Roots掃描範圍的問題,但尚未解決卡表元素如何維護的問題,例如它們什麼時候變髒、誰來把它們變髒等。
有其餘分代區域的對象引用本區域的對象時,其對應的卡表元素就應該變髒,在時間點上也應是:在本區域對象的引用被改變時。解釋執行的字節碼好理解,虛擬機負責每條字節碼指令的執行,能夠交給虛擬機負責;編譯執行的場景,通過即時編譯後的代碼已是純粹的機器指令流了,必須找到一個在機器碼層面的手段,把維護卡表的動做放到每個賦值操做之中。
寫屏障能夠看做在虛擬機層面對「引用類型字段賦值」這個動做的AOP切面。
void oop_field_store (oop* field, oop new_value) { // 引用字段賦值操做 *field = new_value; // 寫後屏障,在這裏完成卡表狀態更新 post_write_barrier(field, new_value); } 複製代碼
周志明-《深刻理解Java虛擬機:JVM高級特性與最佳實踐》第三版
本文由 發給官兵 創做,採用 CC BY 3.0 CN協議 進行許可。 可自由轉載、引用,但需署名做者且註明文章出 處。如轉載至微信公衆號,請在文末添加做者公衆號二維碼。