JVM之GCRoots

《深入JVM之GCRoots》

堆中幾乎存放着Java世界中所有的對象實例,垃圾收集器在對堆回收之前,第一件事情就是要確定這些對象哪些還“存活”着,哪些對象已經“死去”(即不可能再被任何途徑使用的對象)

1.引用計數算法

很多教科書判斷對象是否存活的算法是這樣的:給對象中添加一個引用計數器,每當有一個地方引用它時,計數器值加1;當引用失效時,計數器減1;任何時刻計數器都爲0的對象就是不可能再被使用的。

引用計數算法(Reference Counting)的實現簡單,判斷效率也很高,在大部分情況下它都是一個不錯的算法。但是Java語言中沒有選用引用計數算法來管理內存,其中最主要的一個原因是它很難解決對象之間相互循環引用的問題。

例如:在testGC()方法中,對象objA和objB都有字段instance,賦值令objA.instance=objB及objB.instance=objA,除此之外這兩個對象再無任何引用,實際上這兩個對象都已經不能再被訪問,但是它們因爲相互引用着對象方,異常它們的引用計數都不爲0,於是引用計數算法無法通知GC收集器回收它們。

public class ReferenceCountingGC {
  public Object instance = null;
  private static final int _1MB = 1024 *1024;
  /**
   * 只是爲了佔點內存
   */
  private byte[] bigSize = new byte[2 * _1MB];
  
  public static void main(String[] args) {
    ReferenceCountingGC objA = new ReferenceCountingGC();
    ReferenceCountingGC objB = new ReferenceCountingGC();
    objA.instance = objB;
    objB.instance = objA;
    
    objA = null;
    objB = null;
    
    //假設在這裏發生GC,那麼objA與objB是否會被回收
    System.gc();
    

    
  }
}

運行結果爲:

[GC [DefNew: 234K->64K(960K), 0.0009447 secs][Tenured: 2125K->2189K(4096K), 0.0048757 secs] 2282K->2189K(5056K), [Perm : 365K->365K(12288K)], 0.0058659 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (System) [Tenured: 4237K->141K(6148K), 0.0052656 secs] 4237K->141K(7108K), [Perm : 365K->365K(12288K)], 0.0052973 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
Heap
def new generation total 960K, used 18K [0x23b10000, 0x23c10000, 0x23ff0000)
eden space 896K, 2% used [0x23b10000, 0x23b14818, 0x23bf0000)
from space 64K, 0% used [0x23c00000, 0x23c00000, 0x23c10000)
to space 64K, 0% used [0x23bf0000, 0x23bf0000, 0x23c00000)
tenured generation total 6148K, used 141K [0x23ff0000, 0x245f1000, 0x27b10000)
the space 6148K, 2% used [0x23ff0000, 0x240136b8, 0x24013800, 0x245f1000)
compacting perm gen total 12288K, used 365K [0x27b10000, 0x28710000, 0x2bb10000)
the space 12288K, 2% used [0x27b10000, 0x27b6b578, 0x27b6b600, 0x28710000)
ro space 8192K, 63% used [0x2bb10000, 0x2c023b48, 0x2c023c00, 0x2c310000)
rw space 12288K, 53% used [0x2c310000, 0x2c977f38, 0x2c978000, 0x2cf10000)

在運行結果中可以看到GC日誌中包含"4237K->141K",老年代從4273K(大約4M,其實就是objA與objB)變爲了141K,意味着虛擬並沒有因爲這兩個對象相互引用就不回收它們,這也證明虛擬並不是通過通過引用計數算法來判斷對象是否存活的。大家可以看到對象進入了老年代,但是大家都知道,對象剛創建的時候是分配在新生代中的,要進入老年代默認年齡要到了15纔行,但這裏objA與objB卻進入了老年代。這是因爲Java堆區會動態增長,剛開始時堆區較小,對象進入老年代還有一規則,當Survior空間中同一代的對象大小之和超過Survior空間的一半時,對象將直接進行老年代。

2.根搜算法
在主流的商用程序語言中(Java和C#),都是使用根搜索算法(GC Roots Tracing)判斷對象是否存活的。這個算法的基本思路就是通過一系列名爲"GC Roots"的對象作爲起始點,從這些節點開始向下搜索,搜索所走過的路徑稱爲引用鏈(Reference Chain),當一個對象到GC Roots沒有任何引用鏈相連時,則證明此對象是不可用的,如下圖:

對象bject5, object6, object7雖然有互相判斷,但它們到GC Roots是不可達的,所以它們將會判定爲是可回收對象。
在Java語言裏,可作爲GC Roots對象的包括如下幾種:
a.虛擬機棧(棧楨中的本地變量表)中的引用的對象
b.方法區中的類靜態屬性引用的對象
c.方法區中的常量引用的對象
d.本地方法棧中JNI的引用的對象


GC管理的主要區域是Java堆,一般情況下只針對堆進行垃圾回收。方法區、棧和本地方法區不被GC所管理,因而選擇這些區域內的對象作爲GC roots,被GC roots引用的對象不被GC回收。


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