Java的五大引用

168 阅读5分钟

五大引用概述强引用软引用演示弱引用演示虚引用终结器引用

五大引用概述

基于可达性算法的垃圾回收

image-20221113125807100

  • 实线是强引用,虚线是其他引用

强引用

image-20221113121216580

  • 只有所有 GC Roots 对象都不通过【强引用】引用该对象,该对象才能被垃圾回收
  • 我们平时用new出来的对象,用一个引用来指向这个对象,这个引用是强引用

软引用

软引用用来描述一些还有用,但非必须的对象

image-20221113123644044

  • 通过GC Root对象强引用了我们的软引用对象,然后用软引用对象指向一个对象,这个对象就是被软引用指向的对象

  • 软引用自身也是会占内存的

  • 仅有软引用引用该对象时,在垃圾回收后,内存仍不足时会再次触发垃圾回收,回收软引用对象,可以配合引用队列来释放软引用自身

    • 软引用是在回收了一次,内存还是不够,进行第二次垃圾回收才回收

演示

/**
 * 演示软引用
 * -Xmx20m -XX:+PrintGCDetails -verbose:gc
 */
public class Demo2_3 {

    private static final int _4MB = 4 * 1024 * 1024;


	//这是我们的正常的进行创建对象,是强引用
    public static void main(String[] args) throws IOException {
        List<byte[]> list = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            list.add(new byte[_4MB]);
       }

        System.in.read();


    }
	//这是我们的软引用
    public static void soft() {
        // list --> SoftReference --> byte[]

        List<SoftReference<byte[]>> list = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            SoftReference<byte[]> ref = new SoftReference<>(new byte[_4MB]);
            System.out.println(ref.get());
            list.add(ref);
            System.out.println(list.size());

        }
        System.out.println("循环结束:" + list.size());
        for (SoftReference<byte[]> ref : list) {
            System.out.println(ref.get());
        }
    }
}

强引用执行的结果

image-20221113153150968

  • 会发生推内存溢出的现象,因为都是强引用,所以垃圾回收无法回收

软引用执行的结果

image-20221113153338607

  • 在第五次进行分配空间的时候,内存不够了,第一次进程垃圾回收发现内存还是不够,所以触发第二次的垃圾回收对软引用对象进行回收,所以发现前四个byte数组的内存地址为null

演示配合引用队列对软引用本身进行回收


import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.List;

/**
 * 演示软引用, 配合引用队列
 */
public class Demo2_4 {
    private static final int _4MB = 4 * 1024 * 1024;

    public static void main(String[] args) {
        List<SoftReference<byte[]>> list = new ArrayList<>();

        // 引用队列
        ReferenceQueue<byte[]> queue = new ReferenceQueue<>();

        for (int i = 0; i < 5; i++) {
            // 关联了引用队列, 当软引用所关联的 byte[]被回收时,软引用自己会加入到 queue 中去
            SoftReference<byte[]> ref = new SoftReference<>(new byte[_4MB], queue);
            System.out.println(ref.get());
            list.add(ref);
            System.out.println(list.size());
        }

        // 从队列中获取无用的 软引用对象,并移除
        Reference<? extends byte[]> poll = queue.poll();
        while( poll != null) {
            list.remove(poll);
            poll = queue.poll();
        }

        System.out.println("===========================");
        for (SoftReference<byte[]> reference : list) {
            System.out.println(reference.get());
        }

    }
}
  • queue队列就是我们的引用队列,当我软引用所关联的byte数组对象被回收,软引用就会加到queue中

弱引用

弱引用的功能跟软引用一样,只是强度比软引用还弱

image-20221113124758706

  • 仅有弱引用引用该对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用对象,可以配合引用队列来释放弱引用自身

演示

public class Code_09_WeakReferenceTest {

    public static void main(String[] args) {
//        method1();
        method2();
    }

    public static int _4MB = 4 * 1024 *1024;

    // 演示 弱引用
    public static void method1() {
        List<WeakReference<byte[]>> list = new ArrayList<>();
        for(int i = 0; i < 10; i++) {
            WeakReference<byte[]> weakReference = new WeakReference<>(new byte[_4MB]);
            list.add(weakReference);

            for(WeakReference<byte[]> wake : list) {
                System.out.print(wake.get() + ",");
            }
            System.out.println();
        }
    }

    // 演示 弱引用搭配 引用队列
    public static void method2() {
        List<WeakReference<byte[]>> list = new ArrayList<>();
        ReferenceQueue<byte[]> queue = new ReferenceQueue<>();

        for(int i = 0; i < 9; i++) {
            WeakReference<byte[]> weakReference = new WeakReference<>(new byte[_4MB], queue);
            list.add(weakReference);
            for(WeakReference<byte[]> wake : list) {
                System.out.print(wake.get() + ",");
            }
            System.out.println();
        }
        System.out.println("===========================================");
        Reference<? extends byte[]> poll = queue.poll();
        while (poll != null) {
            list.remove(poll);
            poll = queue.poll();
        }
        for(WeakReference<byte[]> wake : list) {
            System.out.print(wake.get() + ",");
        }
    }

}

弱引用执行的结果

image-20221113160053296

虚引用

虚引用有个最重要的应用就是我们的直接内存分配的ByteBuffer

image-20221113143059909

  • 必须配合引用队列使用,在上的ByteBuffer对象中的cleaner对象就是我们的虚引用,存储的是我们的直接内存的地址,然后我们的ReferenceHandler线程调用cleaner对象的clean方法,然后调用了unsafe.freeMemory来释放我们的直接内存

  • 因为直接内存不受JVM的内存自动管理的控制,所以通过这些方法来间接的实现对直接内存的回收

  • 虚引用的目的

    • 一个对象是否有虚引用完全不会对其生存期间构成影响,也无法通过虚引用来取得一个对象的实例
    • 设置我们虚引用的目的就是为了能在这个对象被回收时系统能收到一个系统通知

终结器引用

即使在可达性分析算法的时候被判为了不可达对象,也不是"非死不可",这时候是暂时处于缓刑阶段,这也是我们的终结器引用的原理

image-20221113145110661

  • finalize()方法是我们的Object中的方法,所以所有对象都有这个方法

  • 对于我们真正宣告一个对象死亡,最多会经历两次标记过程

    • 如果被判定为不可达对象,是第一次标记
    • 随后对这些对象进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法,如果没有覆写过finalize()方法,或者已经调用过了,就是被视为没有必须执行
  • 重写了了finalize方法,A4对象第一次垃圾回收的时候并不是立马被回收,因为重写了finallize方法,所以虚拟机会自动创建一个终结器引用,执行A4对象的第一次回收的时候,会将终结器引用放入引用队列(不会回收A4对象)

  • 由我们的Finalizer线程通过终结器引用找到被引用的对象,并调用他的finallize方法

    • 如果我们的处于缓刑的对象在finalize()方法中被引用链上对象链上了,那么也就是被复活了,也就不会被垃圾回收
    • 如果没复活,第二次GC时才会回收这个A4对象
  • 不推荐使用

    • 因为处理引用队列的线程优先级很低,被处理的机会很少,可能造成这个对象的finalize()迟迟不被调用,导致这块内存迟迟不被回收
    • 而且这个方法的执行,并不保证一定会等待它执行完毕,因为如果执行缓慢,或者死循环,那么导致其他对象回收的等待