JVM判斷對象是否存活

堆中幾乎存放着全部的對象實例,垃圾收集器在對堆回收以前,第一件事情就是要肯定這些對象哪些還「存活」,那些對象已經「死去」(即不可能被任何途徑使用的對象)算法

1、引用計數算法(Reference Counting)

給對象中添加一個引用計數器,每當又一個地方引用它時,計數器值加1;當引用失效時,計數器減1;任什麼時候刻計數器都爲0的對象就是不可能在被使用的.可是Java語言中沒有選用引用計數法來管理內存,其中最主要的緣由是它很難解決對象之間相互循環依賴的問題.bash

例如: 在testGC中,對象objA和對象objB都有字段instance,賦值objA.instance=objB.instance及objB.instance=objA.instance,除此以外這兩個對象再無其餘任何引用,實際上這兩個對象都已經不能再被訪問,可是他們由於他們互相引用着對方,他們的引用計數都不爲0,因而引用計數算法沒法通知GC收集器回收它們. 打印GC詳細信息: -XX:+PrintGCDetailsspa

public class ReferenceCountingGC {

    public Object instance = null;

    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日誌爲:3d

[GC (System.gc()) [PSYoungGen: 334480K->4736K(334848K)] 597914K->270331K(1017536K), 0.0019879 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 
[Full GC (System.gc()) [PSYoungGen: 2408K->0K(298688K)] [ParOldGen: 0K->3363K(682688K)] 3408K->3363K(981376K), [Metaspace: 3162K->3162K(1056768K)], 0.0050515 secs] [Times: user=0.02 sys=0.00, real=0.00 secs] 
Heap
 PSYoungGen      total 153088K, used 3947K [0x0000000715580000, 0x0000000720000000, 0x00000007c0000000)
  eden space 131584K, 3% used [0x0000000715580000,0x000000071595afc0,0x000000071d600000)
  from space 21504K, 0% used [0x000000071d600000,0x000000071d600000,0x000000071eb00000)
  to   space 21504K, 0% used [0x000000071eb00000,0x000000071eb00000,0x0000000720000000)
 ParOldGen       total 349696K, used 872K [0x00000005c0000000, 0x00000005d5580000, 0x0000000715580000)
  object space 349696K, 0% used [0x00000005c0000000,0x00000005c00da3b8,0x00000005d5580000)
 Metaspace       used 3169K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 346K, capacity 388K, committed 512K, reserved 1048576K
複製代碼

GC日誌分析:

GC (minor )日誌:

Full GC 日誌:

後半段分析

對照上面的圖,GC日誌中的PSYoungGen(PS是指Parallel Scavenge)爲Eden+FromSpace,而整個YoungGeneration爲Eden+FromSpace+ToSpace。日誌

咱們設置的新生代大小爲10240K,這包括9216K大小的PSYoungGen和1024K大小的ToSpace。其中,PSYoungGen中的Eden:FromSpace爲8:1,code

這包括8192K的Eden和1024K的FromSpace。cdn

詳細日誌輸出:

參數設置爲:對象

-XX:+PrintGCDetails -XX:-UseAdaptiveSizePolicy -XX:SurvivorRatio=8 -XX:NewSize=10M -XX:MaxNewSize=10Mblog

參數解釋:內存

-XX:+PrintGCDetails 啓用日誌

-XX:-UseAdaptiveSizePolicy 禁用動態調整,使SurvivorRatio能夠起做用

-XX:SurvivorRatio=8 設置Eden:Survivior=8

-XX:NewSize=10M -XX:MaxNewSize=10M 設置整個新生代的大小爲10M

默認垃圾收集器爲:Parallel Scavenge

在運行日誌結果中咱們能夠看到GC日誌包含「2408K->0k」,老年代從2408K(約2M,其實就是objA與objB)變爲了512K,意味着虛擬機並無由於這兩個對象互相引用就不回收它們,這也證實虛擬機並非經過引用計數法判斷對象是否存活的.能夠看到對象進入了老年代,可是你們知道,對象剛建立的時候是分配到新生代中到,要進入老年代須要到new ObjA才行,可是這裏objA和objB卻進入了老年代.這是由於Java堆區會動態增加,剛開始時堆區較小,對象進入老年代還有一些規則,當survior空間中同一代的對象大小之和超過survior空間的一半時,對象將直接進入老年代.

2、可達性分析算法(GC Roots Analysis):主流用這個判斷

這個算法的基本思路是經過一系列名爲「GC Root」的對象做爲起點,從這些節點開始往下搜索,搜索走過的路徑稱爲引用鏈,當一個對象到GC Roots沒有任何引用鏈相連時,則證實此對象是不可用到,下圖對象object5,object6,object7雖然有互相判斷,但它們到GC Roots是不可達到,因此它們將會斷定爲可回收對象

在Java語言裏,可做爲GC Roots對象的包括以下幾種:

  • 一、虛擬機棧(棧楨中的本地變量表)中的引用的對象
  • 二、方法區中的類靜態屬性引用的對象
  • 三、方法區中的常量引用的對象
  • 四、本地方法棧中JNI的引用的對象

3、finalize()方法最終斷定對象是否存活

即便在可達分析算法中不可達的對象,也並不是「非死不可」的,這時候它們暫時處於「緩刑」階段,要真正宣告一個對象死亡,至少要經歷兩次標記過程. 標記的前提是對象在進行可達性分析後發現沒有與GC Roots互相連的引用鏈.

  • 一、第一次標記並進行一次篩選 篩選的條件是此對象是否有必要執行finalize()方法.當對象沒有覆蓋finalize()方法.或者finalize方法已經被虛機調用過,虛擬機將這兩種狀況都視爲「沒有必要執行」,對象被回收
  • 二、第二次標記
相關文章
相關標籤/搜索