1. 程式人生 > >JVM學習記錄-對象已死嗎

JVM學習記錄-對象已死嗎

來看 weak 不可達 關聯 存在 str 棧幀 col ins

前言

先來回顧一下,在jvm運行時數據區,分為兩部分,一個部分是線程共享區,主要包括方法區,另一部是線程私有區分包括本地方法棧虛擬機棧程序計數器。在線程私有部分的三個區域是隨著線程生和滅的。棧中的棧幀隨著方法的進入和退出而執行著出棧和入棧操作。每一個棧幀所用內存大小在類結構確定下來時就已知了。因此這線程私有區的內存分配和回收都具備確定性,簡單概括的說:這部分內存在類加載時分配,在線程結束時回收。(個人理解)

而線程共享區(堆和方法區)則不一樣,一個方法中的多個分支需要的內存可能不一樣,只有在程序處於運行期間時才能知道會創建哪些對象,這部分的內存的分配和回收都是動態的,垃圾收集器(GC)所關註的也是這部分內存,今天要介紹的內容也是圍繞著這部分內存來說的。

對象已死嗎

Java世界中幾乎所有的對象實例都存放在堆裏,垃圾回收器在對堆內存進行回收前,要先確定這些對象中哪些還存活,哪些已死去(死去:不可能再被任何途徑使用的對象);

那麽如何判斷堆內存中的對象是活著還是已經死去了呢?

下面介紹一下主流的兩種判斷對象是否已經死去算法。

引用計數算法

引用計數算法的思想是:給對象添加一個引用計數器,每當有一個地方引用它時,計數器值就加1;當引用失效時,計數器值就減一;任何時刻計數器為0的對象就是不可能再被使用的。

這種算法的特點是實現簡單,判定效率也很高,但是Java虛擬機中卻沒有使用這種算法,主要原因是這種算法很難解決對象之間相互循環引用的問題。

舉個??:先看如下代碼

public class ReferenceCountingTest {
    
    public Object instance = null;

    public void testGC()
    {
        ReferenceCountingTest objectA = new ReferenceCountingTest();
        ReferenceCountingTest objectB = new ReferenceCountingTest();
        
        objectA.instance 
= objectB; objectB.instance = objectA; objectA = null; objectB = null; //假設這時發生GC System.gc(); } }

先分析代碼,objectA和objectB都被設置成了null,在發生GC的時候應該會回收這樣的對象,但是如果按照引用計數算法來看,雖然這兩個對象都設置為null了,但是他們還在互相引用,所以各自的計數器都還不是0,所以還不能進行回收。但是這段代碼在Java虛擬機下的運行結果是兩個對象都已經被回收,這也說明JVM使用的不是引用計數器算法來進行垃圾回收的。

可達性分析算法

在主流的商用程序語言中(Java,C#等)都是通過可達性分析算法來判斷對象是否還存活。這個算法的基本思路是通過一組稱為“GC Roots”的活躍引用(註意不是對象,和書上寫的不一樣)作為起始點,從這些活躍的引用開始向下搜索,搜索走過的路徑稱為引用鏈(Reference Chain),當一個對象到GC Roots沒有任何引用鏈相連時,則證明此對象是不可用的,可以被回收了。

技術分享圖片

上圖中object5、object6、object7、雖然互相有關聯,但是它們到GC roots是不可達的所以他們會被判定為可回收的對象。

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

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

再談引用

JDK1.2之後,Java對引用的概念進行了擴充,將引用分為強引用(Strong Reference)、軟引用(Soft Reference)、弱引用(Weak Reference)、虛引用(Phantom Reference),引用強度依次減弱。

  • 強引用:在程序中普遍存在,類似“Object object = new Object()”;只有強引用存在,對象就不會被回收;
  • 軟引用:有用,但不是必需的。在發生內存溢出異常之前,會把這些對象進行二次回收,如果回收了這部分還是內存不夠,那就會發生內存溢出。(JDK1.2後引入)
  • 弱引用:也是非必需引用,比軟引用更弱一點,弱引用關聯的對象只能活到下一次收集之前,只要收集器一工作,無論內存夠不夠,都會被回收。(JDK1.2後引入)
  • 虛引用:也稱為幽靈引用或者幻影引用,為一個對象設置弱引用的唯一目的就是能在這個對象被收集器回收時收到一個系統通知。(JDK1.2後引入)

生存還是死亡

在JVM中,即使在可達性分析算法中不可達的對象,也並非是“非死不可”的,這時候它們暫處於緩刑階段,一個對象真正死亡,至少要經歷兩次標記過程;

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

如果有必要執行,這個對象會被放在一個F_Queue的隊列中,並在稍後由一個虛擬機自動建立的、低優先級的Finalizer線程去執行finalize()方法,但不承諾會等它執行結束。

稍後進行第二次標記:如果這時候這個對象重新與引用鏈上的任何一個對象建立關聯就會被移除“即將回收的”的集合,如果這個時候還沒有逃脫那基本上就被回收了。

需要註意的是:任何一個對象的finalize()方法都只會被系統自動調用一次。

回收方法區

java虛擬機規範中不要求虛擬機在方法區實現垃圾收集,而且在方法區回收效率很低。

方法區註意回收兩部分內容:廢棄常量和無用的類

判定一個常量比較簡單(一個常量任何地方都沒有被引用了,就可以認為是廢棄常量了),而判定一個類是否是無用類,就相對比較苛刻,需要滿足以下三個條件,才能被認定為是一個無用的類。

  • 該類所有的實例都已經被回收。
  • 加載該類的ClassLoader已經被回收。
  • 該類對應的java.lang.Class對象沒有在任何地方被引用,無法再任何地方通過反射訪問該類的方法。

在大量使用反射、動態代理、Cglib等ByteCode框架、動態生成JSP以及OSGi這類頻繁自定義ClassLoader的場景,都是需要進行無用類回收的。

JVM學習記錄-對象已死嗎