1.背景
在上一節中,我們掌握了垃圾收集的一些演算法,也弄明白了分代回收的原理,
那么HotSpot虛擬機是如何發起記憶體回收的?

2.如何找到GC Roots根節點(列舉根節點)
從可達性分析中GC Roots 節點找參考鏈這個操作為例,可作為GC Roots 的節點主要在全域性的參考(例如常量或類靜態屬性)與執行背景關系(例如堆疊幀中的本地變數表)中,現在的很多應用僅僅方法區就有數百兆,如果要逐個檢查這里面的參考,那么必然會消耗很多時間,
另外,可達性分析對執行時間的敏感還體現在GC停頓上,因為這項分析作業必須在一個能確保一致性的快照中進行—這里的一致性的意思是指在整個分析期間整個執行系統看起來像被凍結在某個時間點上,不可以出現在分析程序中物件參考關系還在不斷的變化,該點不滿足的話分析結果的準確性就無法得到保證,這點導致GC進行時必須停頓所有Java執行執行緒(Sun稱這件事情為“Stop The World”)的其中一個重要的原因,即使在號稱(幾乎)不會發生停頓的CMS收集器中,列舉根節點時也是必須要停頓的,
目前主流的Java虛擬機使用的都是準確式GC,所以當執行系統停頓下來后,并不需要一個不漏的檢查完所有的執行背景關系和全域的參考位置,虛擬機應當是有辦法直接得知哪些地方存在著物件參考,在HotSpot的實作中,是使用一組稱為OopMap的資料結構來達到這個目的的,在類加載完成的時候,HotSpot就把物件內什么偏移量上是什么型別的資料計算出來,在JIT編譯程序中,也會在特定的位置記錄下堆疊和暫存器中哪些位置是參考,這樣,GC在掃描時就可以直接得知這些資訊了,
總結:

3.安全點

3.1.什么是安全點,為什么需要安全點
在 JVM 中如何判斷物件可以被回收 一文中,我們知道 HotSpot 虛擬機采取的是可達性分析演算法,即通過 GC Roots 列舉判定待回收的物件,
那么,首先要找到哪些是 GC Roots,
有兩種查找 GC Roots 的方法:
一種是遍歷方法區和堆疊區查找(保守式 GC)-成本高,
一種是通過 OopMap 資料結構來記錄 GC Roots 的位置(準確式 GC)-可以快速定位,
很明顯,保守式 GC 的成本太高,
準確式 GC 的優點就是能夠讓虛擬機快速定位到 GC Roots,
對應 OopMap 的位置即可作為一個安全點(Safe Point),因為不能為每一條指令都生成對應的OopM,那將會需要大量的額外空間,
在執行 GC 操作時,所有的作業執行緒必須停頓,這就是所謂的”Stop-The-World”,
為什么呢?
因為可達性分析演算法必須是在一個確保一致性的記憶體快照中進行,如果在分析的程序中物件參考關系還在不斷變化,分析結果的準確性就不能保證,
安全點意味著在這個點時,所有作業執行緒的狀態是確定的,JVM 就可以安全地執行 GC ,
3.2.如何選擇安全點
安全點太多,GC 過于頻繁,增大運行時負荷;
安全點太少,GC 等待時間太長,
一般會在如下幾個位置選擇安全點:
1、回圈的末尾
2、方法臨回傳前
3、呼叫方法之后
4、拋例外的位置
3.3.為什么選擇以上的位置作為安全點
主要的目的就是避免程式長時間無法進入 Safe Point,
比如 JVM 在做 GC 之前要等所有的應用執行緒進入安全點,
如果有一個執行緒一直沒有進入安全點,就會導致 GC 時 JVM 停頓時間延長,
比如這里,超大的回圈導致執行 GC 等待時間過長,
3.4.如何在GC發生時,所有執行緒都跑到最近安全點上
主要有兩種方式:
搶斷式中斷:在 GC 發生時,首先中斷所有執行緒,如果發現執行緒未執行到 Safe Point,就恢復執行緒讓其運行到 Safe Point 上,
主動式中斷:在 GC 發生時,不直接操作執行緒中斷,而是簡單地設定一個標志,讓各個執行緒執行時主動輪詢這個標志,發現中斷標志為真時就自己中斷掛起,
JVM 采取的就是主動式中斷,輪詢標志的地方和安全點是重合的,
4.安全區域
4.1.為什么需要安全域
上面講述的Safepoint似乎已經完美的解決了如何進入GC的問題,但實際情況卻不一定,
Safepoint機制保證了程式執行時,在不長的時間內就會遇到可進入GC的Safepoint,
但是,程式不執行的時候呢?所謂的程式不執行就是沒有分配CPU時間,典型的例子就是執行緒處于sleep狀態或者Blocked狀態,這時候執行緒無法回應JVM的中斷請求,“走”到安全的地方去中斷掛起,
JVM顯然也不太可能等待執行緒重新被分配CPU時間,這種情況,就需要安全區域來解決了,
4.2.什么是安全域
Safe Region 是指在一段代碼片段中,參考關系不會發生變化(如sleep的時候),在這個區域內的任意地方開始 GC 都是安全的,
4.3.安全域中如何發起GC
執行緒在進入 Safe Region 的時候先標記自己已進入了 Safe Region,
等到被喚醒時準備離開 Safe Region 時,先檢查能否離開,
如果 GC 完成了,那么執行緒可以離開,
否則它必須等待直到收到安全離開的信號為止,
5.綜合總結
假設HotSpot需要發生GC,首先需要找所有的GC Roots根節點,
但是在記憶體中要想找到所有的根節點,并檢查參考鏈,必然會消耗很多時間,HotSpot肯定不會去掃描整個記憶體去找根節點;
HotSpot是使用一組oopMap資料結構來存放物件的參考,這樣就可以快速準確的完成GC Roots的查找;
但是,問題又來了,如果每一條指令都生成對應的oopMap將會需要大量的額外空間;
那怎么辦呢,我們可以只在需要垃圾回收的點上記錄(生成oopMap)這些資訊,這個點我們就叫這安全點,要發送GC時,等所有的執行緒都運行到這個點上再回收;
但是問題又來了,如果有的執行緒處于sleep或者Blocked狀態呢,這時候這些執行緒無法回應JVM的中斷請求,到達安全點;
這樣這種情況那就是安全域來解決,在安全域中參考關系不會發生變化,所以只要在安全域中就可以直接回收,不需要到達安全點;
完美!
參考:《深入理解Java虛擬機》 周志明 編著:
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/159106.html
標籤:Java
上一篇:動態生成javaBean
