判断对象是否死亡

引用计数算法

给对象添加一个引用计数器,当有一个地方引用时,计数器+1;引用失效时,计数器-1;任何时刻计数器为0的对象就是不可能在被使用的。
引用计数算法判断对象是否存活,但是很难解决对象之间相互循环引用的问题。
举例:

public class ReferenceCountingGC {
    public Object instance= null;
    private static final int _1MB = 1024 * 1024;
    //这个变量唯一意义就是占点内存,以便能在GC日志中看清楚是否被回收过
    private byte [] bjgSize = new byte[5 * _1MB];
    private static void testGC(){
        ReferenceCountingGC objA = new ReferenceCountingGC();
        ReferenceCountingGC objB = new ReferenceCountingGC();
        objA.instance = objB;
        objB.instance = objA;
        objA = null;
        objB = null;
        //这里发生GC
        System.gc();
    }
    public static void main(String args[]){
        testGC();
    }
}

运行结果:
在这里插入图片描述
日志最开始的GC和Full GC表示垃圾回收的停顿类型;
PSYoungGen中最前面的PS代表垃圾收集器是Parallel Scavenge收集器,回收的区域是新生代(YoungGen)
ParOldGen中最前面的Par代表垃圾收集器是Parallel Old收集器,回收的区域是老年代(OldGen).
方括号内的8K->641K(10240K)中表示GC前该内存区域使用容量->GC后该内存区域已使用容量(该内存区域总容量).
768K->641K(19456K)表示GC前Java堆已使用容量->GC后Java对已使用容量(Java对总容量).

objA和objB发生了双向引用,虽然objA和objB都为null,但是它们的引用计数器的值都不为0,如果虚拟机使用的是引用计数算法,则不会回收。
图中gc前是768k,gc后是641k,说明对象被回收了。侧面说明虚拟机并不是通过计数算法判断对象是否存活的。

根搜索算法

主流的商用程序语言都是使用根搜索算法判定对象是否存活的。思路是通过一系列名为“GC Roots”的对象为起始点,从这些节点向下搜索,搜索走过的路径成为引用链,当一个GC Roots没有任何引用链相连(从GC Roots到这个对象不可达)时,则证明该对象不可用。
GC Roots的对象包括:

  • 虚拟机栈(栈帧中本地变量表)中的引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中的常量引用的对象
  • 本地方法栈中native方法引用的变量

判断可回收的对象(GC Roots不可达)

obj1
obj2
obj3

不可回收的对象

GC Roots
obj1
obj2
obj3

生存还是死亡

当GC Roots不可达时也并非“非死不可”,要真正宣告一个对象死亡,至少两次标记过程:进行根搜索时,当GC Roots不可达时进行第一次标记并进行一次筛选,筛选条件是该对象是否需要执行finalize()方法。如果对象没有覆盖finalize()方法或者已经执行过一次finalize()方法,则不再执行;如果需要执行的话,该对象被放在一个F-Queue队列中,由虚拟机自动建立的低优先级的Finalizer的线程去执行该finalize()方法,finalize()方法是对象逃脱死亡命运的最后一次机会,稍后GC对F-Queue队列进行小规模的标记,如果finalize()中成功解救了自己–重新与引用链上的对象进行关联,譬如把自己赋给某个变量,在第二次标记时将被移除“即将回收”的集合。
finalize()方法能做的所有工作,使用try-fin’ally或其他方式可以做的更好,不建议使用。
举例:

	/**
 1. 演示了两点
 2. 1 .对象可以在被GC时自我拯救
 3. 2 .finalize()方法只能被系统自动调用一次
 */
public class FinalizeEscapeGC {

    public static  FinalizeEscapeGC SAVE_HOOK = null;

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("finalize()方法被执行了!");
        FinalizeEscapeGC.SAVE_HOOK = this;
    }
    public static void main(String[] args) throws InterruptedException {
        SAVE_HOOK = new FinalizeEscapeGC();
        ///第一次自救
        SAVE_HOOK = null;
        System.gc();
        //Finalier线程的优先级比较低,等待一秒
        Thread.sleep(1000);
        if(SAVE_HOOK == null){
            System.out.println("已经被干掉了(第一次)!");
        }else{
            System.out.println("我TM还活着(第一次)!");
        }
        //第二次自救
        SAVE_HOOK = null;
        System.gc();
        //Finalier线程的优先级比较低,等待一秒
        Thread.sleep(1000);
        if(SAVE_HOOK == null){
            System.out.println("已经被干掉了(第二次)!");
        }else{
            System.out.println("我TM还活着(第二次)!");
        }
    }
}

运行结果:
在这里插入图片描述

回收方法区

主要回收:废弃常量和无用的类

  1. 废弃常量:与堆中的回收类似,系统中没有引用时,如果必要的话会被回收。

  2. 无用的类:
    - 该类所有实例都已经被回收,就是说堆中不存在该类的实例
    - 加载该类的ClassLoader已经被回收
    - 该类对象的java.lang.Class对象任何地方被引用,无法在任何地方通过反射访问该类的方法。
    满足这三个条件可以进行回收。

    参考《深入理解java虚拟机》

猜你喜欢

转载自blog.csdn.net/administratorJWT/article/details/87882939