java垃圾回收機制--可達性算法


  

  先說一些題外話,Java虛擬機在執行Java程序的過程中會把它所管理的內存划分為若干個不同的數據區,這些區分為線程私有區和線程共享區

  1、線程私有區

    a、程序計數器

    記錄正在執行的虛擬機字節碼指令地址。此區域是是唯一一個在java虛擬機規范中沒有規定任何OutOfMemoryError情況的區域。

   b、Java虛擬機棧

    描述的是Java方法執行的內存模型,每個方法在執行的同時會創建一個棧幀

   c、本地方法棧

    它與虛擬機棧發揮的作用是類似的,它們之間的區別不過是虛擬機棧為虛擬機執行java方法(也就是字節碼)服務,而本地方法棧則為虛擬機使用的Native方法服務。

 

  2、線程共享區

   a、Java堆

    被所有線程共享的一塊內存區域,也是Java虛擬機所管理的內存中最大的一塊。

   b、方法區

    用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編輯器編譯后的代碼等數據,雖然Java虛擬機規范把方法區描述為堆的一個邏輯部分,但是它卻有一個別名Non-Heap(非堆)

 

  下面開始說正題

  目前虛擬機基本都是采用可達性算法,為什么不采用引用計數算法呢?下面就說說引用計數法是如何統計所有對象的引用計數的,再對比分析可達性算法是如何解決引用技術算法的不足。先簡單說說這兩個算法:

  1、引用計數法(reference-counting):每個對象都有一個引用計數器,當對象被引用一次,計數器就加1,當對象引用時效一次就減,當計數器為0,意味着對象是垃圾對象,可以被GC回收。

  2、可達性算法(GC Root Tracing):從GC Root作為起點開始搜索,那么整個連通圖中對象都是活的,對於GC Root無法達到的對象便是垃圾對象,隨時可被GC回收。

  采用引用計數算法的系統只需在每個實例對象創建之初,通過計數器來記錄所有的引用次數即可。而可達性算法,則需要再次GC時,遍歷整個GC根節點來判斷是否回收。

   下面通過一段代碼來對比說明:

  

 public class GcDemo {

    public static void main(String[] args) {
        //分為6個步驟
        GcObject obj1 = new GcObject(); //Step 1
        GcObject obj2 = new GcObject(); //Step 2

        obj1.instance = obj2; //Step 3
        obj2.instance = obj1; //Step 4

        obj1 = null; //Step 5
        obj2 = null; //Step 6
    }
}

class GcObject{
    public Object instance = null;
}

  1、引用計數算法


  如果采用的是引用計數算法:

  再回到前面代碼GcDemo的main方法共分為6個步驟:

  • Step1:GcObject實例1的引用計數加1,實例1的引用計數=1;
  • Step2:GcObject實例2的引用計數加1,實例2的引用計數=1;
  • Step3:GcObject實例2的引用計數再加1,實例2的引用計數=2;
  • Step4:GcObject實例1的引用計數再加1,實例1的引用計數=2;

  執行到Step 4,則GcObject實例1和實例2的引用計數都等於2。

  接下來繼續結果圖:
  

  • Step5:棧幀中obj1不再指向Java堆,GcObject實例1的引用計數減1,結果為1;
  • Step6:棧幀中obj2不再指向Java堆,GcObject實例2的引用計數減1,結果為1。

  到此,發現GcObject實例1和實例2的計數引用都不為0,那么如果采用的引用計數算法的話,那么這兩個實例所占的內存將得不到釋放,這便產生了內存泄露。

   

  2、可達性算法


  這是目前主流的虛擬機都是采用GC Roots Tracing算法,比如Sun的Hotspot虛擬機便是采用該算法。 該算法的核心算法是從GC Roots對象作為起始點,利用數學中圖論知識,圖中可達對象便是存活對象,

  而不可達對象則是需要回收的垃圾內存。這里涉及兩個概念,一是GC Roots,一是可達性。

  那么可以作為GC Roots的對象(見下圖):

  • 虛擬機棧的棧幀的局部變量表所引用的對象;
  • 本地方法棧的JNI所引用的對象;
  • 方法區的靜態變量和常量所引用的對象;

  關於可達性的對象,便是能與GC Roots構成連通圖的對象,如下圖:
  

 

 



  從上圖,reference1、reference2、reference3都是GC Roots,可以看出:

  • reference1-> 對象實例1;
  • reference2-> 對象實例2;
  • reference3-> 對象實例4;
  • reference3-> 對象實例4 -> 對象實例6;

  可以得出對象實例1、2、4、6都具有GC Roots可達性,也就是存活對象,不能被GC回收的對象。
  而對於對象實例3、5直接雖然連通,但並沒有任何一個GC Roots與之相連,這便是GC Roots不可達的對象,這就是GC需要回收的垃圾對象。

  到這里,相信大家應該能徹底明白引用計數算法和可達性算法的區別吧。

  再回過頭來看看最前面的實例,GcObject實例1和實例2雖然從引用計數雖然都不為0,但從可達性算法來看,都是GC Roots不可達的對象。

  總之,對於對象之間循環引用的情況,引用計數算法,則GC無法回收這兩個對象,而可達性算法則可以正確回收。

 

 

 

  參考資料:https://blog.csdn.net/lamp_zy/article/details/53212909


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM