天天看點

深入了解java虛拟機(三)對象回收判斷算法以及死亡過程

在堆裡面存放着Java幾乎所有的對象執行個體,垃圾收集器要進行垃圾回收,要做的第一步便是找出那些對象是需要回收的。      

怎麼判斷對象是否需要回收?

常用的方法有兩種。

1、引用計數算法。為每一個對象添加一個引用計數器,每當有人持有對其的一個引用的時候,該計數器加1。這種算法(Reference Counting)實作簡單,判斷效率高,是一個很不錯的算法,如Python語言、COM和Squirrel中都用它來管理記憶體。但是主流的Java虛拟機實作中并沒有使用這個算法,主要原因是它很難解決對象之間循環引用的問題。比如在對象A中持有一個指向B的成員,而在對象B中又有一個指向A的成員,那麼這兩個對象将無法被回收,造成了記憶體洩露。

2、可達性分析算法。通過一系列的稱為“GC Roots的對象作為起始點,從這些節點開始向下搜尋,搜尋所走過的路徑稱為引用鍊(Reference Chain)。當一個對象從GC Roots不可達時,則證明這個對象時應該被回收的。這樣,即使對象A和B互有引用,但卻是GC Roots不可達的對象,它們已然會被垃圾回收器回收。

在Java中,可作為GC Roots對象包括下面幾種:

        1、虛拟機棧中引用的對象

        2、方法區中類靜态屬性引用的對象

        3、方法區中常量引用的對象

        4、本地方法棧中JNI引用的對象

這兩種算法判斷都是通過引用貫穿其中。但是這種引用的定義過于狹隘。在JDK1.2之前,如果Reference類型的資料中存儲的數值代表的是另一塊記憶體的起始位址,則稱這塊内容代表着一個引用。但有時候,我們希望能有這樣一種對象:當記憶體充足的時候能保留在記憶體中,當記憶體較為緊張的時候,能回收這些對象。JDK1.2之後,Java對引用的概念進行了擴充,分為4類:強引用(Strong Reference)、軟引用(Soft Reference)、弱引用(Weak Reference)、虛引用(Phantom Reference)。

        1、普通代碼中預設的都是強引用,例如Object obj = new Object()。隻要引用還在,永遠不會回收。

        2、軟引用描述一些非必須的對象,隻有當記憶體将要發生溢出異常之前才會将其回收。使用SoftReference類來實作。

        3、弱引用也用來描述非必須的對象,但是隻能生存島下一次垃圾收集發生之前。使用WeakReference類來實作。

        4、虛引用也成為幽靈引用,它的存在不會影響一個對象的生存時間,也無法通過虛引用擷取一個對象執行個體。他的存在隻是為了能在這個對象被收集器回收時能獲得一個系統通知。使用PhantomReference類實作。

被回收的對象何時死亡?

通過引用計數或可達性分析确定一個對象可被回收直到該對象死亡要經過兩次标記過程:

1、當第一次被标記為不可達時,它将會進行一次篩選,篩選條件是此對象時候有必要執行finalize()方法。訪對象沒有覆寫finalize()或已經被調用過,則虛拟機認為它“沒必要執行”。如果這個對象被判定為有必要執行finalize()方法,則會将這個對象放置在F-Queue的隊列之中,并在稍後一個由虛拟機自動建立、低優先級的Finalizer線程去執行它。這裡的“執行”僅代表虛拟機會觸發這個方法,并不一定會等待它運作結束。防止由于finalize()方法執行緩慢或者發生死循環等導緻其它對象處于等待狀态。finalize()方法是對象逃脫死亡厄運最後的機會,可以通過将this指針指派給某個類變量或者對象的成員變量來拯救自己。

2、觸發finalize()方法之後,稍後GC将對F-Queue中的對象進行第二次小規模标記。如果對象在finalize()中成功拯救了自己,則它将會被移除出“即将回收”的集合。否則之後它就真的被回收了。

[java] view plain copy

  1. package org.bupt.xiaoye;  
  2. /** 
  3.  * 此代碼示範了兩點:  
  4.  * 1.對象可以在被GC時自我拯救。  
  5.  * 2.這種自救的機會隻有一次,因為一個對象的finalize()方法最多隻會被系統自動調用一次 
  6.  */  
  7. public class FinalizeEscapeGC {  
  8.     public static FinalizeEscapeGC SAVE_HOOK = null;  
  9.     public void isAlive() {  
  10.         System.out.println("yes, i am still alive :)");  
  11.     }  
  12.     @Override  
  13.     protected void finalize() throws Throwable {  
  14.         super.finalize();  
  15.         System.out.println("finalize mehtod executed!");  
  16.         FinalizeEscapeGC.SAVE_HOOK = this;  
  17.     public static void main(String[] args) throws Throwable {  
  18.         SAVE_HOOK = new FinalizeEscapeGC();  
  19.         //對象第一次成功拯救自己  
  20.         SAVE_HOOK = null;  
  21.         System.gc();  
  22.         // 因為Finalizer方法優先級很低,暫停0.5秒,以等待它。防止finalize雖然被觸發但是沒有執行完成。注釋掉sleep方法後,會出現finalize沒有執行完而程式就退出的情況  
  23.         Thread.sleep(500);  
  24.         if (SAVE_HOOK != null) {  
  25.             SAVE_HOOK.isAlive();  
  26.         } else {  
  27.             System.out.println("no, i am dead :(");  
  28.         }  
  29.         // 下面這段代碼與上面的完全相同,但是這次自救卻失敗了  
  30.         // 因為Finalizer方法優先級很低,暫停0.5秒,以等待它  
  31. }  

finalize()方法并不建議使用,因為它運作代價高昂,不确定性太大,而且不能保證各個對象之間的調用順序。

方法區的垃圾回收

Java虛拟機規範中并沒有強制要求虛拟機必須實作方法區(HotSpot中的永久代)的垃圾回收,而且方法區中進行垃圾收集的“成本效益”比較低:在堆中,尤其是新生代中,正常應用進行一次垃圾收集一般可以回收70%~95%的空間,而永久代的垃圾收集效率遠低于此。

方法區中垃圾收集主要分為兩部分:廢棄常量和無用的類

      廢棄常量回收與Java堆中對象的收集非常類似。如果沒有任何地方引用這些常量便會被系統清理。

     無用的類的回收就比較複雜。隻有該類滿足下面三個條件才算是“無用的類”:

                1、該類的所有實力都已經被回收

                2、加載該類的ClassLoader已經被回收了

                3、該類對應的java.lang.Class對象沒有在被任何地方被引用,無法再任何地方通過反射通路該類的方法

        當然滿足了這3個條件,并不是一定會回收。HotSpot提供了-Xnoclassgc參數來控制。-Xnoclassgc 每次永久存儲區滿了後一般GC 算法在做擴充配置設定記憶體前都會觸發一次FULL GC,除非設定了-Xnoclassgc.   在大量使用反射、動态代理、CGLib等ByteCode架構、動态生成JSP以及OSGi這類頻繁自定義ClassLoader的場景都需要虛拟機具備類解除安裝功能。