Java虚拟机——对象已死?

文章讨论了Java中垃圾收集器如何判断对象是否存活,主要涉及引用计数算法和可达性分析算法。引用计数法简单但有局限,而可达性分析是Java等语言的主要判断方式,通过GCRoots确定对象的可达性。对象死亡需要经过两次标记,且有机会通过finalize()方法自救,但不推荐依赖此方法。另外,方法区的垃圾收集效果通常不如Java堆明显。
摘要由CSDN通过智能技术生成
  • 堆里面放着Java世界中的所有的对象实例,垃圾收集堆在对堆进行回收前的第一件事情就是确定,这些对象,哪些还活着,哪些已经死了。("死去"即不可能再被任何途径使用的对象)

判断对象是否存活?

  1. 引用计数算法
    在这里插入图片描述
  • 这种算法虽然会占用一定的内存空间,但是它的原理简单,判定效率也高,大多数情况下都是一个不错的算法。
  • 不过主流的Java虚拟机里面没有选用技术算法来管理内存。 这个算法有很多例外情况需要考虑,必须要配合大量额外处理才能进行工作
  • 举一个例子
public class ReferenceCountingtGC {
    
    public Object instance = null;
    
    public static final int _1MB = 1024 * 1024;
    
    //这个成员属性的唯一意义就是占点内存,以便在GC日志中看清楚是否回收过
    private byte[] bigSize = new byte[2 * _1MB];
    
    public static void testGC(){
        ReferenceCountingtGC objA = new ReferenceCountingtGC();
        ReferenceCountingtGC objB = new ReferenceCountingtGC();
        objA.instance = objB;
        objB.instance = objA;
        
        //假设在这发生GC,objA和objB能否被回收?
        System.gc();
    }
}
  • 这两个对象已经不可能再被访问了,但是它们因为互相对方,导致它们的引用计数都不为零。 所以引用计数算法就无法回收它们。

2. 可达性分析算法

  • 主流的商用程序语言Java、C#、Lisp的内存管理子系统,都是通过可达性分析算法来判定对象是否存活。
  • 这个算法的基本思路就是通过一系列称为"GC Roots"的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为引用链
  • 如果某个对象到GC Roots间没有任何引用链相连。用图论的话来说就是GC Roots到这个对象不可达时,说明对象不可能再被使用。
    在这里插入图片描述
    在这里插入图片描述
  • 后文还会提到分代收集和局部回收。 如果只针对Java堆中某一块区域发起垃圾收集时,必须考虑到内存区域是虚拟机自己的实现细节,更不是孤立封闭的。
  • 所以某个区域里的对象 完全有可能被 位于堆中其他区域的对象 所引用,这个时候就需要将这些关联区域的对象也一并加入到GC Roots集合中去。

再谈引用

  • 无论是通过引用计数算法 判断 对象的引用数量。 还是通过可达性分析算法判断对象是否引用链可达。
  • 判定对象是否存活都和"引用"离不开关系。
    在这里插入图片描述
    在这里插入图片描述

生存还是死亡?

  • 即使在可达性分析算法中判定为 不可达的对象,也不是非死不可的。
  • 这个时候它们还处于"缓刑"阶段,要真正宣告一个对象死亡,最多会经历两次标记过程
  1. 如果在进行可达性分析后发现 没有与GC Roots相连接的引用链,那它将会被第一次标记
  • 随后进行一次筛选,筛选的条件就是此对象是否有必要执行finalize()方法。 如果对象没有覆盖finalize方法或者finalize方法已经被虚拟机调用过。 那么虚拟机将这两种情况视为没有必要执行。
  • 如果这个对象被判定为确有必要执行finalize方法,那么该对象会被放置在一个F-Queue队列之中,并在稍后由一条由虚拟机自动建立的、低调度优先级的Finalizer线程去执行它们的finalize方法。
    在这里插入图片描述
  1. finalize方法是对象逃脱死亡的最后机会,收集器将会对F-Queue中的对象进行第二次小规模的标记。如果对象要拯救自己,那么需要重新与引用链上的任何一个对象建立关联即可。
public class FinalizeEscapeGC {

    public static FinalizeEscapeGC SAVE_HOOK = null;

    public void isAlive(){
        System.out.println("yes , i am still alive");
    }

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("finalize method executed!");
        FinalizeEscapeGC.SAVE_HOOK = this;
    }

    public static void main(String[] args)  throws Throwable{

        SAVE_HOOK = new FinalizeEscapeGC();

        //对象第一次成功拯救自己
        SAVE_HOOK = null;
        System.gc();
        //因为Finalizer方法优先级很低,暂停0.5s,以等待它。
        Thread.sleep(500);
        if( SAVE_HOOK != null){
            SAVE_HOOK.isAlive();
        }else{
            System.out.println("no , i am dead");
        }


        //第二次自救失败
        SAVE_HOOK = null;
        System.gc();
        //因为Finalizer方法优先级很低、暂停0.5秒,以等待它
        Thread.sleep(500);
        if(SAVE_HOOK != null){
            SAVE_HOOK.isAlive();
        }else{
            System.out.println("no , i am dead");
        }
    }
}

在这里插入图片描述
在这里插入图片描述

  • 但是不建议在Java中使用finalize方法来拯救对象。 try-finally或使用其他方法都可以做的更好。

回收方法区

  • *中提到过可以不要求虚拟机在方法区中实现垃圾收集,方法区垃圾收集的"性价比"通常也是比较低的。
  • 在Java堆中,尤其是在新生代中,对常规应用进行一次垃圾回收通常都可以回收70%或者99%的内存空间。 但是方法区由于苛刻的判定条件,其区域垃圾收集的回收成果往往远低于此。
    在这里插入图片描述
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值