JVM 垃圾回收器(Garbage Collector)需要判斷哪些對象是“垃圾”,即不再被程序使用的對象,以便回收它們占用的內存。JVM 主要使用以下兩種方法來判斷對象是否是垃圾:
1. 引用計數算法 (Reference Counting):
-
原理:
- 為每個對象維護一個引用計數器。
- 當一個對象被引用時,引用計數器加 1。
- 當一個對象的引用被置為
null
或超出作用域時,引用計數器減 1。 - 當一個對象的引用計數器為 0 時,表示該對象不再被任何地方引用,可以被回收。
-
優點:
- 實現簡單: 只需要維護一個計數器。
- 實時性高: 當對象的引用計數器變為 0 時,可以立即回收該對象。
-
缺點:
- 無法解決循環引用問題: 如果兩個或多個對象相互引用,即使它們不再被其他對象引用,它們的引用計數器也不會變為 0,導致無法回收。
public class ReferenceCountingGC {public Object instance = null;public static void main(String[] args) {ReferenceCountingGC objA = new ReferenceCountingGC();ReferenceCountingGC objB = new ReferenceCountingGC();objA.instance = objB; // objA 引用 objBobjB.instance = objA; // objB 引用 objAobjA = null; // objA 不再引用對象objB = null; // objB 不再引用對象// 此時,objA 和 objB 相互引用,引用計數器都不為 0,無法被回收 (即使它們已不再被使用)// ...} }
-
HotSpot VM 是否使用: HotSpot VM 不使用引用計數算法,因為它無法解決循環引用問題。
2. 可達性分析算法 (Reachability Analysis) (根搜索算法):
-
原理:
- 從一組稱為 “GC Roots” 的根對象開始,沿著對象引用鏈進行遍歷。
- 如果一個對象到 GC Roots 之間沒有任何引用鏈相連,則說明該對象不可達,可以被回收。
- 可達的對象會被標記, 不可達的對象被認為是垃圾.
-
GC Roots:
- 虛擬機棧 (VM Stack) 中引用的對象: 局部變量、方法參數等引用的對象。
- 方法區中類靜態屬性引用的對象:
static
變量引用的對象。 - 方法區中常量引用的對象:
final
修飾的常量引用的對象。 - 本地方法棧中 JNI (Java Native Interface) 引用的對象: Native 方法引用的對象。
- Java 虛擬機內部的引用: 例如, 基本數據類型對應的 Class 對象, 一些常駐的異常對象(NullPointerException, OutOfMemoryError 等), 系統類加載器.
- 被同步鎖 (synchronized 關鍵字) 持有的對象。
- 反應Java虛擬機內部情況的 JMXBean、JVMTI 中注冊的回調、本地代碼緩存等。
-
優點:
- 可以解決循環引用問題。
- 準確性高。
-
缺點:
- 需要暫停應用程序 (Stop-The-World): 在進行可達性分析時,需要暫停所有 Java 線程,以保證分析結果的準確性。
- 實現復雜。
-
HotSpot VM 是否使用: HotSpot VM 使用可達性分析算法。
對象死亡的判定 :
即使通過可達性分析算法判斷一個對象不可達,它也不會立即被回收。對象真正死亡需要經歷至少兩次標記過程:
-
第一次標記:
- 如果對象在進行可達性分析后發現沒有與 GC Roots 相連接的引用鏈,則會被第一次標記。
- 會進行篩選, 判斷此對象是否有必要執行
finalize()
方法。 - 如果對象沒有覆蓋
finalize()
方法,或者finalize()
方法已經被虛擬機調用過,則會被判定為“沒有必要執行”。 - 如果對象覆蓋了
finalize()
方法, 且還沒有被調用過, 則會將該對象放置在一個名為 F-Queue 的隊列中。
-
finalize()
方法的執行 (如果有):- 稍后由一條由虛擬機自動建立的、低調度優先級的 Finalizer 線程去執行它們的
finalize()
方法。 - 注意:
finalize()
方法是對象逃脫死亡的最后一次機會。- 在
finalize()
方法中,只要將對象重新與引用鏈上的任何一個對象建立關聯(例如,將this
賦值給某個類變量或對象的成員變量),就可以避免被回收。 finalize()
方法只會被系統自動調用一次。如果對象在finalize()
方法中逃脫了死亡,下次再被標記時,finalize()
方法不會再被執行。- 不建議使用
finalize()
方法進行資源釋放,因為它的執行時間不確定,可能會導致資源長時間得不到釋放。 finalize
方法可能會導致性能問題,應盡量避免使用。
- 稍后由一條由虛擬機自動建立的、低調度優先級的 Finalizer 線程去執行它們的
-
第二次標記:
- 如果在
finalize()
方法中對象成功逃脫了死亡,則不會被回收。 - 如果在
finalize()
方法中對象沒有逃脫死亡,或者對象沒有覆蓋finalize()
方法,或者finalize()
方法已經被虛擬機調用過,則會被第二次標記。 - 被第二次標記的對象會被放置在一個名為 F-Queue 的隊列中, 等待被回收.
- 如果在
總結:
JVM 使用可達性分析算法來判斷對象是否是垃圾。從 GC Roots 開始,沿著對象引用鏈進行遍歷,如果一個對象到 GC Roots 之間沒有任何引用鏈相連,則說明該對象不可達,可以被回收。即使對象不可達,也不會立即被回收,需要經歷至少兩次標記過程,并且有機會在 finalize()
方法中逃脫死亡。