Skip to main content

引用的类型

huhxAbout 3 minjavaJavaJvm

在Java中提供了4个级别的引用:强引用、软引用、弱引用和虚引用。之所以整出这么多的引用类型,是因为引用也有轻重缓急,称做为强软弱虚,分门别类有助于GC更好的进行垃圾回收。下面我们就来具体看下这些引用类型。

public class Reference {
    @Override
    protected void finalize() throws Throwable {
        System.out.println("finalize");
    }
}

之所以重写finalize也仅仅是用于此次的测试,一旦Reference对象被回收,finalize方法就会被调用。

Caution

在实际应用中,这个方法永远不要去重写。

强引用

强引用就是程序中一般使用的引用类型,强引用的对象是可触及的,不会被回收。下面是一个强引用的例子

public class ReferenceMain {
    public static void main(String[] args) throws IOException {
        var reference = new Reference();
        reference = null;

        System.gc(); // 显示的进行垃圾回收
        System.in.read(); // 只是阻塞当前线程, GC是在另外一个线程执行
    }
}

强引用具备以下特点:

  • 强引用可以直接访问目标对象
  • 强引用所指向的对象在任何时候都不会被系统回收,虚拟机宁愿抛出OOM异常,也不会回收强引用所指向的对象
  • 强引用可能导致内存泄露

软引用

如果一个对象只持有软引用,那么当堆空间不足时,就会被回收。软引用使用java.lang.ref.SoftReference类实现。

public class ReferenceMain {
    public static void main(String[] args) throws IOException, InterruptedException {
        // 在堆上面分配10M的内存
        var softReference = new SoftReference<>(new byte[1024 * 1024 * 10]);
        System.out.println(softReference.get());

        System.gc();
        TimeUnit.SECONDS.sleep(1);
        System.out.println(softReference.get());

        // 再分配一个15M的数组,堆(20M)将装不下。这时候系统垃圾回收器先回收一次,如果不够,会将软引用回收掉
        byte[] bytes = new byte[1024 * 1024 * 15];
        System.out.println(softReference.get());
    }
}

运行时,修改JVM参数-Xms20M -Xmx20M,设置堆内存为固定大小20M。有如下结果

[B@372f7a8d
[B@372f7a8d
null

Info

使用场景:图片缓存
图片缓存框架中,内存缓存的图片是以软引用这种方式保存,使得 JVM 在发生 OOM 之前,可以回收这部分缓存。此外还可以用在网页缓存上。

弱引用

在系统GC时,只要发现弱引用,不管系统堆空间使用情况如何,都会将对象进行回收。但是,由于垃圾回收器的线程通常优先级很低,并不一定能很快地发现持有弱引用的对象。在这种情况下,弱引用对象可以存在较长的时间。

一旦一个弱引用对象被垃圾回收器回收,便会加入一个注册的引用队列。弱引用使用java.lang.ref.WeakReference类实现。

public class ReferenceMain {
    public static void main(String[] args) throws IOException, InterruptedException {
        var weakReference = new WeakReference<>(new Reference());

        System.out.println(weakReference.get());
        System.gc();
        System.out.println(weakReference.get());

        var t1 = ThreadLocal.withInitial(Reference::new);
        t1.remove();
    }
}

输出结果

com.huhx.thread.reference.Reference@2f92e0f4
null
finalize

使用场景:容器WeakHashMap

在静态内部类中,经常会使用虚引用。例如:一个类发送网络请求,承担 callback 的静态内部类,则常以虚引用的方式来保存外部类的引用,当外部类需要被 JVM 回收时,不会因为网络请求没有及时回应,引起内存泄漏。

虚引用

虚引用是所有引用类型中最弱的一个。一个持有虚引用的对象,和没有引用几乎是一样的,随时都可能被垃圾回收器回收。当试图通过虚引用的get()方法取得强引用时,总会失败。并且,虚引用必须和引用队列一起使用,它的作用在于跟踪垃圾回收过程。

当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象后,将这个虚引用加入引用队列,以通知应用程序对象的回收情况。

使用场景:Jvm, Netty(回收堆外内存)

FAQ

总结

参考