JVM史上最全的图文剖析对象可达性分析

虚拟机如何确定一个对象是不是垃圾

引用计数算法

使用一个引用计数器记录该对象还有多少个引用指针指向该对象,此算法简单高效但需要在代码中进行额外的逻辑处理以防止循环引用导致内存泄露的问题。

让我们来一起看看下面的例子来理解循环引用内存泄漏两个概念:

/**
 * @author Zeng
 * @date 2020/4/6 11:41
 */
public class ReferenceCountingGC {

    public Object instance = null;
    private static final int _1MB = 1024 * 1024;
    /**
     * 占点内存,以便观察清楚GC日志中是否有进行垃圾收集
     */
    private byte[] bigSize = new byte[2 * _1MB];
    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("finalize method executed!");
    }
    public static void testGC(){
        ReferenceCountingGC objA = new ReferenceCountingGC();
        ReferenceCountingGC objB = new ReferenceCountingGC();
        //对象内部的instance引用指针相互指向对方实例
        objA.instance = objB;
        objB.instance = objA;
		//除了instance指针以外没有其它引用指针指向这两个对象
        objA = null;
        objB = null;
        System.gc();
        //执行finalize()方法的速度有些慢,让主线程等待一下它执行
        Thread.sleep(500);
    }
    public static void main(String[] args) {
        testGC();
    }
}
循环引用

在引用计数算法中对于objAobjB是无法回收的,因为它们内部含有对方实例的引用指针,但是除此之外没有其它指针引用这两个对象,也无法访问到这两个对象,JVM无法回收这两个对象,这就导致了内存泄漏

执行结果如下:

执行结果

可以看到JVM里面并不是采用引用计数算法,因为在显式指定垃圾收集时JVM确实把这两个对象给回收了,这两个对象的finalize()方法被调用了,这个方法是当对象第一次被回收时被调用的。

那么JVM是如何确定对象是不是一个“垃圾”呢?

对象可达性分析

通过一系列的“GC Roots”根对象作为起始节点,一直往下搜索引用关系,搜索过程所走过的路径称为“引用链”。

看下面的代码可以构成一条引用关系链,而objD因为没有指针引用它而成为了垃圾,等待下一次垃圾回收来了结它。

/**
 * @author Zeng
 * @date 2020/4/7 7:44
 */
public class ReferenceList {

    private String name;
    public Object instance = null;
    
    public ReferenceList(String name) {
        this.name = name;
    }
    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println(name + " finalize method executed!");
    }
    
    public static void main(String[] args) throws InterruptedException {
        ReferenceList objA = new ReferenceList("objA");
        ReferenceList objB = new ReferenceList("objB");
        ReferenceList objC = new ReferenceList("objC");
        objA.instance = objB;
        objB.instance = objC;
        objB = null;
        objC = null;
        ReferenceList objD = new ReferenceList("objD");
        objD = null;
        System.gc();
        Thread.sleep(500);
    }

}

上面代码得到的引用关系链如下图所示:

引用关系链

所以objD会被JVM回收,而objA、objB、objC可以存活下来,如下图所示可以验证这个结果:

objD对象被回收

固定作为GC Roots的对象主要有以下几种:

虚拟机栈中引用的对象,例如局部变量形式参数临时变量······

类静态属性引用的变量,例如类的静态引用类型成员变量

常量引用的对象,例如String str = "alive"字符串常量池中的引用

同步锁引用的对象,例如synchronized(obj)中被锁住的obj可作为GC Roots

细分的四种引用类型指定对象被回收的时机

强引用(Strongly Reference):即类似于Object objA = new Object()这种引用关系,垃圾收集器是永远不会回收掉强引用的对象的。

软引用(Soft Reference):用于表示还有用,但非必须的对象,只有在发生内存溢出之前,才会回收这类对象。

弱引用(Weak Reference):被弱引用关联的对象只能存活到下一次垃圾回收发生为止,无论内存是否可用,都会回收该类对象。

虚引用(Phantom Reference):最差劲的一种引用关系,无法通过虚引用获取对象实例,设置虚引用的目的就是为了让它成为垃圾被回收掉

下面例子可以证明它们引用回收的时机

import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;

/**
 * @author Zeng
 * @date 2020/4/7 8:16
 * 四种引用类型实践
 */
public class Reference {

    private String name;
    public Reference(String name) {
        this.name = name;
    }
    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println(name + " finalize method executed!");
    }
    public static void main(String[] args) throws InterruptedException {
        //强引用
        Reference obj = new Reference("强引用obj");
        System.gc();
        Thread.sleep(500);
        //软引用
        Reference objA = new Reference("软引用objA");
        SoftReference<Reference> softReferenceA = new SoftReference<>(objA);
        objA = null;
        System.gc();
        Thread.sleep(500);
        //弱引用
        Reference objB = new Reference("弱引用objB");
        WeakReference<Reference> weakReferenceB = new WeakReference<>(objB);
        objB = null;
        System.gc();
        Thread.sleep(500);
        //虚引用
        Reference objC = new Reference("虚引用objC");
        ReferenceQueue<Reference> phantomQueue = new ReferenceQueue<>();
        PhantomReference<Reference> phantomReference = new PhantomReference<>(objC, phantomQueue);
        objC = null;
        System.gc();
        Thread.sleep(500);
    }
}

运行结果:

运行结果

可以看到弱引用和虚引用在下一轮垃圾回收时都会被当成垃圾给回收掉,而强引用和软引用没有被回收,如果构造出堆溢出的情况,软引用也会被回收。

总结

本文主要讲解了JVM如何确定一个对象是否可以回收,以及介绍引用计数法和可达性分析两种方法判断对象是否可回收,最后通过Java的四种引用类型的实践验证了强引用、软引用、弱引用和虚引用的垃圾回收时机,如果有任何错误欢迎提出,乐意与大家交流学习!

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值