JVM 判斷是否應該回收對象

JVM內存 中程序計數器、虛擬機棧、本地方法棧 3 個區域隨線程而生,隨線程而滅。這幾個區域不用考慮回收問題,因爲方法結束後 或者 線程結束後,內存就跟着回收了。而Java堆方法區不一樣,一個接口的多個實現類需要的內存不一樣,一個方法的多個分支需要的內存也可能不一樣,我們只有在程序運行的時候才知道會創建那些對象,這部分內存的分配和回收都是動態的,垃圾收集主要所關注的就是Java堆方法區。

1. 可達性分析算法

可達性分析算法,這個算法的思想,通過一系列的稱爲 GC Roots 的起始點,當一個對象到 GC Roots 沒有任何的引用鏈相連,則證明這個對象是不可用的,這個對象是可回收的。

GCRootS 對象包括如下幾種。

  • 虛擬機棧(棧幀中的本地變量表)中引用的對象。
  • 方法區中類靜態屬性引用的對象。
  • 方法區中常量引用的對象。
  • 本地方法棧中 JNI (即一般說的Native方法)引用的對象。

我們知道,每個方法執行的時候,JVM 都會創建一個相應的棧幀(棧幀中包括操作數棧、局部變量表、運行時常量池的引用),棧幀中包含這在方法內部使用的所有對象的引用(當然還有其他的基本類型數據),當方法執行完後,該棧幀會從虛擬機棧中彈出,這樣一來,臨時創建的對象的引用(虛擬機棧中)也就不存在了,或者說沒有任何 GCRootS 指向這些臨時對象(堆中),這些對象在下一次GC時便會被回收掉。

靜態屬性是該類型(class)的屬性,不單獨屬於任何實例,因此該屬性自然會作爲GCRootS 。只要這個class存在,該引用指向的對象也會一直存在。Class 也是會被回收的,請看第四小節。

2 引用

引用的定義:如果 reference 類型的數據(棧幀中的本地變量表)中存儲的數值代表的是另外一塊內存的起始地址,就稱這塊內存代表着一個引用。一個對象如果只有兩種狀態:引用和未被引用,如下情況無法去實現,以類對象,內存空間還足夠的時候,可以保存這類對象,內存空間不夠的話,則拋棄(回收)這些對象。

Java的引用類型分爲:強引用,軟引用,弱引用,虛引用。

強引用:Object object = new Object(); 這類引用,只要強引用存在,就不會回收被引用的對象(堆中)。

軟引用:用來描述一些還有用,但並非必須的對象,在內存將要溢出的時候,會對於軟應用的對象進行回收,如果這次回收之後內存還是不夠,纔會報出異常。

弱引用:弱引用都只能活到下次垃圾回收之前,無論內存是否足夠,都會進行回收。

虛引用:最弱的一種引用關係,一個對象是否有虛引用的存在完全不會對其生存時間構成影響,也無法通過虛引用來獲取實例,爲一個對象設置虛引用就是這個對象被垃圾收集器回收時受到一個系統通知。

3. 生存還是死亡

可達性分析方法中不可達的對象也不是“非死不可”的,這時候它們處於“緩刑”階段,要真正宣告一個對象死亡還需要兩次標記過程。

第一次標記:如果對象進行可達性分析後,發現沒有與GC Roots 相連接的引用鏈,那麼他會被第一次標記並且進行一次篩選,篩選的條件是此對象是否有必要執行 finalize 方法,當對象沒有覆蓋 finalize 方法 或者 finalize 方法已經被虛擬機調用過,虛擬機將這兩種情況都是爲“沒必要執行”。

如果有必要執行,則將他們添加到隊列F-Queue的隊列之中,並在稍後由一個虛擬機自動建立、低優先級的 Finalizer 線程去執行它,這裏所謂的“執行”是指虛擬機會觸發這個方法,但並不承諾會等他運行結束,這樣做的原因,如果一個對象在 finalize() 方法中執行緩慢,或者發生了死循環,導致其他對象一直等待, finalize() 方法是逃脫被回收的最後一次機會。

第二次標記:GC會再次對 F-Queue中的對象進行第二次小規模的標記,如果對象要在finalize()中拯救自己,只要重新與引用鏈上的任何一個對象建立關聯即可,例如:把自己賦值給某個類變量或者對象的成員變量,那在第二次標記時他將被移除“即將回收”的集合;如果對象這時候還沒有逃脫,那基本上它就真的被回收了。

4. 回收方法區 

很多人認爲方法區(或者HotSpot虛擬機的永久代)是沒有垃圾收集的,Java 虛擬機規範中確實說過可以不要求虛擬機在方法區實現垃圾收集,而且在方法區中進行垃圾收集的“性價比”較低;在堆中,尤其是在新生代,常規應用進行一次垃圾收集一般可以回收 70%~95% 的空間,而永久代的垃圾收集效率遠低於此。

永久代的垃圾收集主要回收兩部分內容:廢棄常量和無用的類

回收廢棄常量與回收 Java 堆中的對象非常相似。例如:有一個常量字符串 “abc”,如果沒有String 對象使用它,就回收這個常量字符串。

判斷類是否爲廢棄類,需要滿足三個條件。

  • 該類的所有實例都被回收,也就是Java堆中不存在該類的任何實例。
  • 加載該類的 ClassLoader 已經被回收。
  • 該類對應的 Class 對象沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法。

在大量使用反射、動態代理、CGLib 等框架中、動態生成 JSP 以及 OSGI 這類頻繁自定義 ClassLoader 的場景都需要虛擬機具備卸載的功能,以保證永久代不會溢出。

參考文獻

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章