一次性把Java的四种引用说清楚!

前几天在CodeReview的时候,看到了一个用 WeakHashMap
代码,进而聊到了 WeakReference
,再聊到Java四种引用类型。

回想了一下,上次学习Java的强软弱虚四种引用类型,还是在准备面试的时候。平时用得不多,一下子竟然想不清楚它们的区别,只记得它们的强度依次递减。

下来又看了一下这方面的文章,今天好好把它们理清楚。

四种引用的区别

其实四种引用的区别在于GC的时候,对它们的处理不同。用一句话来概括,就是:如果一个对象GC Root可达,强引用不会被回收,软引用在内存不足时会被回收,弱引用在这个对象第一次GC会被回收。

如果GC Root不可达,那不论什么引用,都会被回收

虚引用比较特殊,等于没有引用,不会影响对象的生命周期,但可以在对象被收集器回收时收到一个系统通知。

下面结合案例分别来讲一下四种引用在面对GC时的表现以及它们的常见用途。先设置一下JVM参数

-Xms20M -Xmx20M -Xmn10M -verbose:gc -XX:+PrintGCDetails
复制代码

这就是我们平时最常使用的引用。只要GC的时候这个对象GC Root可达,它就不会被回收。如果JVM内存不够了,直接抛出OOM。比如下面这段代码就会抛出 OutOfMemoryError

public static void main(String[] args) {
    List<Object> list = new LinkedList<>();
    for (int i = 0; i < 21; i++) {
        list.add(new byte[1024 * 1024]);
    }
}
复制代码

软引用,当GC的时候,如果GC Root可达,如果内存足够,就不会被回收;如果内存不够用,会被回收。将上面的例子改成软引用,就不会被OOM:

public static void main(String[] args) {
    List<Object> list = new LinkedList<>();
    for (int i = 0; i < 21; i++) {
        SoftReference<byte[]> softReference = new SoftReference<>(new byte[1024 * 1024]);
        list.add(softReference);
    }
}
复制代码

我们把程序改造一下,打印出GC后的前后的差别:

public static void main(String[] args) {
    List<SoftReference<byte[]>> list = new LinkedList<>();
    for (int i = 0; i < 21; i++) {
        SoftReference<byte[]> softReference = new SoftReference<>(new byte[1024 * 1024]);
        list.add(softReference);
        System.out.println("gc前:" + softReference.get());
    }
    System.gc();
    for (SoftReference<byte[]> softReference : list) {
        System.out.println("gc后:" + softReference.get());
    }
}
复制代码

会发现,打印出的日志,GC前都是有值的,而GC后,会有一些是 null
,代表它们已经被回收。

而我们设置的堆最大为20M,如果把循环次数改成15,就会发现打印出的日志,GC后没有为 null
的。但通过 -verbose:gc -XX:+PrintGCDetails
参数能发现,JVM还是进行了几次GC的,只是由于内存还够用,所以没有回收。

public static void main(String[] args) {
    List<SoftReference<byte[]>> list = new LinkedList<>();
    for (int i = 0; i < 15; i++) {
        SoftReference<byte[]> softReference = new SoftReference<>(new byte[1024 * 1024]);
        list.add(softReference);
        System.out.println("gc前:" + softReference.get());
    }
    System.gc();
    for (SoftReference<byte[]> softReference : list) {
        System.out.println("gc后:" + softReference.get());
    }
}
复制代码

所以软引用的常见用途就呼之欲出了:缓存。尤其是那种希望这个缓存能够持续时间长一点的。

软引用,只要这个对象发生GC,就会被回收。

把上面的代码改成软引用,会发现打印出的日志,GC后全部为 null

public static void main(String[] args) {
    List<WeakReference<byte[]>> list = new LinkedList<>();
    for (int i = 0; i < 15; i++) {
        WeakReference<byte[]> weakReference = new WeakReference<>(new byte[1024 * 1024]);
        list.add(weakReference);
        System.out.println("gc前:" + weakReference.get());
    }
    System.gc();
    for (WeakReference<byte[]> weakReference : list) {
        System.out.println("gc后:" + weakReference.get());
    }
}
复制代码

所以弱引用也适合用来做缓存,不过由于它是只要发生GC就会被回收,所以存活的时间比软引用短得多,通常用于做一些非常临时的缓存。

我们知道, WeakHashMap
内部是通过弱引用来管理entry的。它的键是“弱键”,所以在GC时,它对应的键值对也会从Map中删除

Tomcat中有一个 ConcurrentCache
,用到了 WeakHashMap
,结合 ConcurrentHashMap
,实现了一个线程安全的缓存,感兴趣的同学可以研究一下源码,代码非常精简,加上所有注释,只有短短59行。

ThreadLocal
中的静态内部类 ThreadLocalMap
里面的entry是一个 WeakReference
的继承类。

使用弱引用,使得 ThreadLocalMap
知道 ThreadLocal
对象是否已经失效,一旦该对象失效,也就是成为垃圾,那么它所操控的Map里的数据也就没有用处了,因为外界再也无法访问,进而决定擦除Map中相关的值对象,Entry对象的引用,来保证Map总是保持尽可能的小。

虚引用的设计和上面三种引用有些不同,它并不影响GC,而是为了在对象被GC时,能够收到一个系统通知。

那它是怎么被通知的呢?虚引用必须要配合 ReferenceQueue
,当GC准备回收一个对象,如果发现它还有虚引用,就会在回收之前,把这个虚引用加入到与之关联的 ReferenceQueue
中。

NIO是如何利用虚引用来管理内存的呢?

DirectBuffer
直接从Java堆之外申请一块内存, 这块内存是不直接受JVM GC管理的, 也就是说在GC算法中并不会直接操作这块内存. 这块内存的GC是由于DirectBuffer在Java堆中的对象被GC后, 通过一个通知机制, 而将其清理掉的.

DirectBuffer内部有一个 Cleaner
。这个Cleaner是PhantomReference的子类。当DirectBuffer对象被回收之后, 就会通知到PhantomReference。然后由ReferenceHandler调用 tryHandlePending()
方法进行 pending
处理. 如果pending不为空, 说明DirectBuffer被回收了, 就可以调用Cleaner的 clean()
进行回收了。

上面这个方法的代码在 Reference
类里面,有兴趣的同学可以去看一下那个方法的源码。

以上就是Java中四种引用的区别。一般来说,强引用我们都知道,虚引用很少用到。而软引用和弱引用的区别在于回收的时机:软引用GC时,发现内存不够才回收,弱引用只要一GC就会回收。

关于作者

微信公众号:编了个程

个人网站https://yasinshaw.com

笔名Yasin,一个有深度,有态度,有温度的程序员。工作之余分享编程技术和生活,如果喜欢我的文章,可以顺手

关注

一下公众号,也欢迎

转发

分享给你的朋友~

在公众号回复“面试”或者“学习”可以领取相应的资源哦~

一次性把Java的四种引用说清楚!

原文 

https://juejin.im/post/5ec3c4aae51d45788a6d4e33

本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。

PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处:Harries Blog™ » 一次性把Java的四种引用说清楚!

赞 (0)
分享到:更多 ()

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址