HotSpot虛擬機使用可達性分析算法肯定對象是否能夠被GC。java
可達性分析算法從一系列GCRoot對象開始,向下搜索引用鏈,若是一個對象沒有與任何GCRoot對象關聯,這個對象就會被斷定爲可回收對象。算法
GCRoot包括如下對象:安全
虛擬機棧上的本地變量表引用的對象多線程
方法區中類的靜態屬性引用的對象函數
方法區中常量引用的對象spa
本地方法棧中JNI引用的對象線程
這一過程稱爲根節點枚舉,也就是垃圾回收中的標記過程,當前全部的垃圾收集器,在標記階段都必須中止全部java執行線程(STW),以保證對象引用狀態不會發生變化。對象
HotSpot虛擬機做爲準確式虛擬機,維護了一個專門的映射表(OopMap)記錄哪些位置存放着對象引用,來快速完成根節點枚舉過程。內存
爲每個操做記錄OopMap不現實,HotSpot虛擬機引入了SafePoint。字符串
SafePoint是程序中的某些位置,線程執行到這些位置時,線程中的某些狀態是肯定的,在safePoint能夠記錄OopMap信息,線程在safePoint停頓,虛擬機進行GC。
線程停頓方式有兩種,搶先式中斷和主動式中斷:
搶先式中斷:虛擬機須要GC時,中斷全部線程,讓沒有到達SafePoint的線程繼續執行至SafePoint並中斷
主動式中斷:虛擬機不直接中斷線程,而是在內存中設置標誌位,線程檢查到標誌位被設置,運行至SafePoint時主動中斷
SafePoint通常出如今如下位置:
循環體的結尾
方法返回前
調用方法的call以後
拋出異常的位置
這些位置保證線程不會長時間運行而沒法到達SafePoint,避免其餘線程都停頓等待本線程。
咱們知道代碼是在線程裏執行的, GC的代碼也是在線程裏執行, 若是執行GC的時候其餘線程也同時執行的話, heap的狀態將是難以追蹤的. 以上面的代碼爲例, 假設GC線程經過掃描線程的stack(線程stack是一種GC Root), 掃描到demoObject, 而後根據這時候, main函數執行到[3], 但還未執行, 掃描的結果只發現demoObject是存活的, 接下來, main函數的線程執行[3], demoObject.val1引用了一個字符串對象, 這個對象的掃描就漏掉了, 除非以某種方式記錄下這個變化, 而後從新掃描demoObject. 即使有辦法記錄這個賦值致使的變化而後再次掃描, 若是其餘線程這時候又來搗亂, 那麼從新掃描的時候有可能又發生了變化, 陷入循環…
再往下一點, 咱們知道CPU執行運算時的數據, 須要從內存裏載入寄存器中, 運算完再從寄存器存入內存, 對象的地址也要通過這麼個過程. 假如一個java線程分配了一個對象A, 該對象的地址存在某個寄存器中, 而後線程的cpu時間片到期被切換出去, 同時GC的線程開始掃描存活對象, 因爲沒有路徑到這個地址還在寄存器中的對象, 這個對象被認爲是garbage, 回收了. 而後睡眠的java線程醒來了, 把寄存器中的對象地址賦值給了存活對象的某個字段, over…
GC的目的在於幫助咱們收集再也不使用的內存, 可是把正在是使用的內存當成垃圾回收顯然是不能接受的. 同時經過分析也看到, 因爲多線程運行環境的存在, GC的工做會變的異常複雜, 要安全的回收垃圾, 須要具有兩個條件:
heap的變化是受限的, 固然了, 全部線程都停下來最好, 這樣heap 在GC過程當中是穩定的,這是最簡單的狀況.
heap的狀態是已知的, 不會有活着的對象找不到或者很難找的狀況. 想一想對象地址在寄存器中的狀況, 雖然能夠有辦法能夠掃描線程的寄存器, 即便這樣, 也必須知道哪一個寄存器在某個時刻存的是地址, 要作到掃描不漏是很複雜的事情.
SafePoint沒法解決線程未達到SafePoint並處於休眠或等待狀態的狀況,此時引入SafeRegion的概念。
SafeRegion是代碼中的一塊區域或線程的狀態,在SafeRegion中,線程執行與否不會影響對象引用的狀態。線程進入SafeRegion會給本身加標記,告訴虛擬機能夠進行GC;線程準備離開SafeRegion前會詢問虛擬機GC是否完成。