本節內容的概要如下; 對象已死嗎? 一、判斷對象是否存活的演算法 1、引用計數器演算法 給對象中添加一個引用計數器,每當有一個地方引用它時,計數器值就加1;當引用失效時,計數器值就減1;任何時刻計數器為0的對象就是不可能再被使用的。 客觀地說,引用計數演算法(Reference Counting)的實現簡 ...
本節內容的概要如下;
對象已死嗎?
一、判斷對象是否存活的演算法
1、引用計數器演算法
給對象中添加一個引用計數器,每當有一個地方引用它時,計數器值就加1;當引用失效時,計數器值就減1;任何時刻計數器為0的對象就是不可能再被使用的。
客觀地說,引用計數演算法(Reference Counting)的實現簡單,判定效率也很高,在大部分情況下它都是一個不錯的演算法,但是,至少主流的Java虛擬機裡面沒有選用引用計數演算法來管理記憶體,其中最主要的原因是它很難解決對象之間相互迴圈引用的問題。
2、可達性分析演算法
這個演算法的基本思路就是通過一系列的稱為“GC Roots”的對象作為起始點,從這些節點開始向下搜索,搜索所走過的路徑稱為引用鏈(Reference Chain),當一個對象到GC Roots沒有任何引用鏈相連(用圖論的話來說,就是從GC Roots到這個對象不可達)時,則證明此對象是不可用的。
如圖3-1所示,對象object 5、object 6、object 7雖然互相有關聯,但是它們到GC Roots是不可達的,所以它們將會被判定為是可回收的對象。
圖3-1 可達性分析演算法判定對象是否可回收
在Java語言中,可作為GC Roots的對象包括下麵幾種:
- 虛擬機棧(棧幀中的本地變數表)中引用的對象。
- 方法區中類靜態屬性引用的對象。
- 方法區中常量引用的對象。
- 本地方法棧中JNI(即一般說的Native方法)引用的對象。
二、引用類型
在JDK 1.2之後,Java對引用的概念進行了擴充,將引用分為強引用(Strong Reference)、軟引用(Soft Reference)、弱引用(Weak Reference)、虛引用(Phantom Reference)4種,這4種引用強度依次逐漸減弱。
1、強引用
強引用就是指在程式代碼之中普遍存在的,類似“Object obj = new Object()”這類的引用,只要強引用還存在,垃圾收集器永遠不會回收掉被引用的對象。
2、軟引用
軟引用是用來描述一些還有用但並非必需的對象。對於軟引用關聯著的對象,在系統將要發生記憶體溢出異常之前,將會把這些對象列進回收範圍之中進行第二次回收。如果這次回收還沒有足夠的記憶體,才會拋出記憶體溢出異常。在JDK 1.2之後,提供了SoftReference類來實現軟引用。
3、弱引用
弱引用也是用來描述非必需對象的,但是它的強度比軟引用更弱一些,被弱引用關聯的對象只能生存到下一次垃圾收集發生之前。當垃圾收集器工作時,無論當前記憶體是否足夠,都會回收掉只被弱引用關聯的對象。在JDK 1.2之後,提供了WeakReference類來實現弱引用。
4、虛引用
虛引用也稱為幽靈引用或者幻影引用,它是最弱的一種引用關係。一個對象是否有虛引用的存在,完全不會對其生存時間構成影響,也無法通過虛引用來取得一個對象實例。為一個對象設置虛引用關聯的唯一目的就是能在這個對象被收集器回收時收到一個系統通知。在JDK 1.2之後,提供了PhantomReference類來實現虛引用。
三、 生存還是死亡
即使在可達性分析演算法中不可達的對象,也並非是“非死不可”的,這時候它們暫時處於“緩刑”階段,要真正宣告一個對象死亡,至少要經歷兩次標記過程:如果對象在進行可達性分析後發現沒有與GC Roots相連接的引用鏈,那它將會被第一次標記並且進行一次篩選,篩選的條件是此對象是否有必要執行finalize()方法。當對象沒有覆蓋finalize()方法,或者finalize()方法已經被虛擬機調用過,虛擬機將這兩種情況都視為“沒有必要執行”。
如果這個對象被判定為有必要執行finalize()方法,那麼這個對象將會放置在一個叫做F-Queue的隊列之中,併在稍後由一個由虛擬機自動建立的、低優先順序的Finalizer線程去執行它。這裡所謂的“執行”是指虛擬機會觸發這個方法,但並不承諾會等待它運行結束,這樣做的原因是,如果一個對象在finalize()方法中執行緩慢,或者發生了死迴圈(更極端的情況),將很可能會導致F-Queue隊列中其他對象永久處於等待,甚至導致整個記憶體回收系統崩潰。finalize()方法是對象逃脫死亡命運的最後一次機會,稍後GC將對F-Queue中的對象進行第二次小規模的標記,如果對象要在finalize()中成功拯救自己——只要重新與引用鏈上的任何一個對象建立關聯即可,譬如把自己(this關鍵字)賦值給某個類變數或者對象的成員變數,那在第二次標記時它將被移除出“即將回收”的集合;如果對象這時候還沒有逃脫,那基本上它就真的被回收了。
四、回收方法區
方法區(或者HotSpot虛擬機中的永久代),進行垃圾收集的“性價比”一般比較低。
永久代的垃圾收集主要回收兩部分內容:廢棄常量和無用的類。
如何判定廢棄常量:沒有任何對象引用
如何判定一個類是否是“無用的類”,類需要同時滿足下麵3個條件才能算是“無用的類”:
- 該類所有的實例都已經被回收,也就是Java堆中不存在該類的任何實例。
- 載入該類的ClassLoader已經被回收。
- 該類對應的java.lang.Class 對象沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法。
在大量使用反射、動態代理、CGLib等ByteCode框架、動態生成JSP以及OSGi這類頻繁自定義ClassLoader的場景都需要虛擬機具備類卸載的功能,以保證永久代不會溢出。