Java虚拟机02——对象存活判断和4种引用

·  阅读 664

对象存活判断

垃圾收集器在对堆进行回收前,第一件事情就是要确定这些对象之中哪些还“存活”着,哪些已经“死去”

引用计数法

给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时候计数器为0的对象是不可能再被使用的。

  • 缺点:难以解决对象之间互相循环引用的问题

例子:引用计数算法的缺陷

-XX:+PrintGCDetails 通过此命令可以打印GC信息

public class RefrenceCountingGC {
    public Object instance = null;
    private static final int _1MB=1024* 1024;

    private byte[] bigSize = new byte[2 * _1MB];

    public static void testGC() {
        RefrenceCountingGC objA = new RefrenceCountingGC();
        RefrenceCountingGC objB = new RefrenceCountingGC();
        objA.instance = objB;
        objB.instance = objA;

        objA = null;
        objB = null;

        // 调用GC
        System.gc();
    }

    public static void main(String[] args) {
        testGC();
    }
}
复制代码

image.png

上面例子objA与objB互相依赖,从结果来看,内存大小从7014k -> 832k,虚拟机进行了回收,证明虚拟机不是通过引用计数法来判断存活的。

可达性分析算法

通过一系列的成为“GC Roots” 的对象作为起点,从这些节点开始向下搜索,当一个对象到GC Roots没有任何GC链连接时(从GC Roots到这个对象不可达)则证明这个对象是不可活的。如图:

image.png

GC Roots的对象包括下面几种

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

4种引用

经过上面描述得知,对象的存活都与“引用”有关。在JDK1.2之后,Java对引用的概念进行了扩充,将引用分为强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Week Reference)、虚引用(Phantom Reference),引用强度依次减弱

强引用 > 软引用 > 弱引用 > 虚引用

强引用

是使用最普遍的引用,类似“Object obj = new Object()”。只要强引用还存在,垃圾收集器就不会回收被引用的对象

软引用

描述一些还有用但并非必须的对象。在系统将要发生内存溢出之前,将会把这些对象列进回收范围之中进行二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。

弱引用

描述非必须对象,强度比软引用还弱一些。被弱引用关联的对象只能生存到下次垃圾收集之前。当垃圾收集器工作时,无论当内存是否足够,都会回收掉只被弱引用关联的对象。

虚引用

一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来获得一个对象实例。为一个对象设置虚引用关联的唯一目的就是在这个对象被收集回收时收到一个系统通知、

4种引用代码实践

判断对象是否存活

如果通过可达性算法分析一个对象不可达,此时会被第一次标记,并且进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。当对象没有覆盖该方法或已经被执行了,则不需要执行finalize()方法。

如果判定需要执行finalize()方法,那这个对象将会放在F-Queue的队列中,虚拟机会触发这个方法,但不承诺会等待它运行结束(如果方法缓慢,将导致崩溃)。稍后GC将会对F-Queue进行第二次标记,并把标记的对象移到“即将回收”的集合中。

综上,finalize()函数是在JVM回收内存时执行的,仅执行一次,但JVM并不保证在回收内存时一定会调用。

测试引用例子

先创建一个Demo类,重写它的finallize()方法,如果被GC了,则会打印信息

public class RefrenceDemo {

    @Override
    protected void finalize() throws Throwable {
        System.out.println("哎呀,我被回收了");
        super.finalize();
    }
}
复制代码

测试强引用

测试代码:

    public static void main(String[] args) {
        RefrenceDemo demo = new RefrenceDemo();
        System.gc();
    }
复制代码

结果: 无任何输出,则证明强引用对象没有被回收

测试软引用

测试代码:

VM参数: -Xms20m -Xmx20m

    public static void main(String[] args) {
        List<String> temp = new ArrayList<>();
        SoftReference<RefrenceDemo> ref = new SoftReference<>(new RefrenceDemo());
        for (int i = 0; i < 10000; i++) {
            temp.add(String.valueOf(i));
        }
        System.gc();
    }	
复制代码

运行代码,此时内存充足,并没有输出任何结果

将代码中的10000改成100000

    public static void main(String[] args) {
        List<String> temp = new ArrayList<>();
        SoftReference<RefrenceDemo> ref = new SoftReference<>(new RefrenceDemo());
        for (int i = 0; i < 100000; i++) {
            temp.add(String.valueOf(i));
        }
        System.gc();
    }
复制代码

运行结果:

image.png

此时,finalize()方法被执行了,说名内存不足,需要回收软引用的对象。

测试弱引用

VM参数: -Xms20m -Xmx20m

将上例的10000缩小到1000,此时内存空间是足够的。

     public static void main(String[] args) {
        WeakReference<RefrenceDemo> ref = new WeakReference<>(new RefrenceDemo());

        System.out.println(ref.get());
        
        List<String> temp = new ArrayList<>();
        for (int i = 0; i < 1000; i++) {
            temp.add(String.valueOf(i));
        }

        System.gc();
    }
复制代码

运行结果:

image.png

在GC前,引用还存活,GC后便执行了finalize()方法,说明弱引用只能活到GC前

测试虚引用

VM参数: -Xms20m -Xmx20m

    public static void main(String[] args) {
        ReferenceQueue queue = new ReferenceQueue();
        PhantomReference<RefrenceDemo> ref = new PhantomReference<>(new RefrenceDemo(),queue);

        System.out.println(ref.get());

        System.gc();
    }

复制代码

运行结果:

image.png

这说明虚引用在实例化后,就被终止了

分类:
后端
标签:
收藏成功!
已添加到「」, 点击更改