
在OopMap的協助下,HotSpot可以快速準确地完成GC Roots枚舉,但一個很現實的問題随之而來:可能導緻引用關 系變化,或者說導緻OopMap内容變化的指令非常多,如果為每一條指令都生成對應的OopMap,那将會需要大量的 額外存儲空間,這樣垃圾收集伴随而來的空間成本就會變得無法忍受的高昂。 實際上HotSpot也的确沒有為每條指令都生成OopMap,隻是在“特定的位置”記錄了這些資訊,這些位置被稱為安全 點(Safepoint)。有了安全點的設定,也就決定了使用者程式執行時并非在代碼指令流的任意位置都能夠停頓下來開 始垃圾收集,而是強制要求必須執行到達安全點後才能夠暫停。是以,安全點的標明既不能太少以至于讓收集器等待 時間過長,也不能太過頻繁以至于過分增大運作時的記憶體負荷。安全點位置的選取基本上是以“是否具有讓程式長時 間執行的特征”為标準進行標明的,因為每條指令執行的時間都非常短暫,程式不太可能因為指令流長度太長這樣的 原因而長時間執行,“長時間執行”的最明顯特征就是指令序列的複用,例如方法調用、循環跳轉、異常跳轉等都屬于 指令序列複用,是以隻有具有這些功能的指令才會産生安全點。
如何在垃圾收集發生時讓所有線程都跑到最近的安全點?
有兩種方案可供選擇:搶先式中斷(Preemptive Suspension)和主動式中斷(Voluntary Suspension)
搶先式中斷不需要線程的執行代碼主動去配合,在垃圾收集發生時,系統首先把所有使用者線程全部中斷,如果發現有 使用者線程中斷的地方不在安全點上,就恢複這條線程執行,讓它一會再重新中斷,直到跑到安全點上。現在幾乎 沒有虛拟機實作采用搶先式中斷來暫停線程響應GC事件。
主動式中斷的思想是當垃圾收集需要中斷線程的時候,不直接對線程操作,僅僅簡單地設定一個标志位,各個 線程執行過程時會不停地主動去輪詢這個标志,一旦發現中斷标志為真時就自己在最近的安全點上主動中斷挂起。 輪詢标志的地方和安全點是重合的,另外還要加上所有建立對象和其他需要在Java堆上配置設定記憶體的地方,這是為 了檢查是否即将要發生垃圾收集,避免沒有足夠記憶體配置設定新對象。