写点什么

Java 四种引用类型:强引用、软引用、弱引用、虚引用

用户头像
简爱W
关注
发布于: 2020 年 09 月 12 日
Java四种引用类型:强引用、软引用、弱引用、虚引用

Java 为什么要设计这四种引用

Java 的内存分配和内存回收,都不需要程序员负责,都是由伟大的 JVM 去负责,一个对象是否可以被回收,主要看是否有引用指向此对象,说的专业点,叫可达性分析。


Java 设计这四种引用的主要目的有两个:


  1. 可以让程序员通过代码的方式来决定某个对象的生命周期;

  2. 有利用垃圾回收。


强引用

强引用是最普遍的一种引用,我们写的代码,99.9999%都是强引用:

Object o = new Object();
复制代码

这种就是强引用了,是不是在代码中随处可见,最亲切。


只要某个对象有强引用与之关联,这个对象永远不会被回收,即使内存不足,JVM 宁愿抛出 OOM,也不会去回收。


那么什么时候才可以被回收呢?当强引用和对象之间的关联被中断了,就可以被回收了。


我们可以手动把关联给中断了,方法也特别简单:

o = null;
复制代码

我们可以手动调用 GC,看看如果强引用和对象之间的关联被中断了,资源会不会被回收,为了更方便、更清楚的观察到回收的情况,我们需要新写一个类,然后重写 finalize 方法,下面我们来进行这个实验:

public class Student {    @Override    protected void finalize() throws Throwable {        System.out.println("Student 被回收了");    }}public static void main(String[] args) {    Student student = new Student();    student = null;    System.gc();}
复制代码

运行结果:

Student 被回收了
复制代码

可以很清楚的看到资源被回收了。


当然,在实际开发中,千万不要重写 finalize 方法


在实际的开发中,看到有一些对象被手动赋值为 NULL,很大可能就是为了“特意提醒”JVM 这块资源可以进行垃圾回收了。点击这里获取一份 JVM 实战教程。


软引用

下面先来看看如何创建一个软引用:


 SoftReference<Student>studentSoftReference=new SoftReference<Student>(new Student());
复制代码

软引用就是把对象用 SoftReference 包裹一下,当我们需要从软引用对象获得包裹的对象,只要 get 一下就可以了:

SoftReference<Student>studentSoftReference=new SoftReference<Student>(new Student());Student student = studentSoftReference.get();System.out.println(student);
复制代码

软引用有什么特点呢:


当内存不足,会触发 JVM 的 GC,如果 GC 后,内存还是不足,就会把软引用的包裹的对象给干掉,也就是只有在内存不足,JVM 才会回收该对象。


还是一样的,必须做实验,才能加深印象:

SoftReference<byte[]> softReference = new SoftReference<byte[]>(new byte[1024*1024*10]);System.out.println(softReference.get());System.gc();System.out.println(softReference.get());        byte[] bytes = new byte[1024 * 1024 * 10];System.out.println(softReference.get());
复制代码

我定义了一个软引用对象,里面包裹了 byte[],byte[]占用了 10M,然后又创建了 10Mbyte[]。


运行程序,需要带上一个参数:

-Xmx20M
复制代码


代表最大堆内存是 20M。


运行结果:

[B@11d7fff[B@11d7fffnull
复制代码

可以很清楚的看到手动完成 GC 后,软引用对象包裹的 byte[]还活的好好的,但是当我们创建了一个 10M 的 byte[]后,最大堆内存不够了,所以把软引用对象包裹的 byte[]给干掉了,如果不干掉,就会抛出 OOM。


软引用到底有什么用呢?比较适合用作缓存,当内存足够,可以正常的拿到缓存,当内存不够,就会先干掉缓存,不至于马上抛出 OOM。说到缓存,大家可以关注微信公众号 Java 技术栈获取更多干货。


弱引用

弱引用的使用和软引用类似,只是关键字变成了 WeakReference:

WeakReference<byte[]> weakReference = new WeakReference<byte[]>(new byte[1024\*1024\*10]);System.out.println(weakReference.get());
复制代码

弱引用的特点是不管内存是否足够,只要发生 GC,都会被回收:

WeakReference<byte[]> weakReference = new WeakReference<byte[]>(new byte[1]);System.out.println(weakReference.get());System.gc();System.out.println(weakReference.get());
复制代码

运行结果:

[B@11d7fffnull
复制代码

可以很清楚的看到明明内存还很充足,但是触发了 GC,资源还是被回收了。 

弱引用在很多地方都有用到,比如 ThreadLocal、WeakHashMap。


虚引用

虚引用又被称为幻影引用,我们来看看它的使用:

ReferenceQueue queue = new ReferenceQueue();PhantomReference<byte[]> reference = new PhantomReference<byte[]>(new byte[1], queue);System.out.println(reference.get());
复制代码

虚引用的使用和上面说的软引用、弱引用的区别还是挺大的,我们先不管 ReferenceQueue 是个什么鬼,直接来运行:

null
复制代码

竟然打印出了 null,我们来看看 get 方法的源码:

public T get() {          return null;}
复制代码


这是几个意思,竟然直接返回了 null。


这就是虚引用特点之一了:无法通过虚引用来获取对一个对象的真实引用。


那虚引用存在的意义是什么呢?这就要回到我们上面的代码了,我们把代码复制下,以免大家再次往上翻:

ReferenceQueue queue = new ReferenceQueue();PhantomReference<byte[]> reference = new PhantomReference<byte[]>(new byte[1], queue);System.out.println(reference.get());
复制代码


创建虚引用对象,我们除了把包裹的对象传了进去,还传了一个 ReferenceQueue,从名字就可以看出它是一个队列。


虚引用的特点之二就是 虚引用必须与 ReferenceQueue 一起使用,当 GC 准备回收一个对象,如果发现它还有虚引用,就会在回收之前,把这个虚引用加入到与之关联的 ReferenceQueue 中。


我们来用代码实践下吧:

ReferenceQueue queue = new ReferenceQueue();List<byte[]> bytes = new ArrayList<>();PhantomReference<Student> reference = new PhantomReference<Student>(new Student(),queue);new Thread(() -> {    for (int i = 0; i < 100;i++ ) {        bytes.add(new byte[1024 * 1024]);    }}).start();new Thread(() -> {    while (true) {        Reference poll = queue.poll();        if (poll != null) {            System.out.println("虚引用被回收了:" + poll);        }    }}).start();Scanner scanner = new Scanner(System.in);scanner.hasNext();}
复制代码

运行结果:

Student 被回收了虚引用被回收了:java.lang.ref.PhantomReference@1ade6f1
复制代码

我们简单的分析下代码: 

第一个线程往集合里面塞数据,随着数据越来越多,肯定会发生 GC。 

第二个线程死循环,从 queue 里面拿数据,如果拿出来的数据不是 null,就打印出来。


从运行结果可以看到:当发生 GC,虚引用就会被回收,并且会把回收的通知放到 ReferenceQueue 中。


虚引用有什么用呢?在 NIO 中,就运用了虚引用管理堆外内存.


用户头像

简爱W

关注

还未添加个人签名 2020.07.22 加入

还未添加个人简介

评论 (1 条评论)

发布
用户头像
您好,我是开源中国的⼯作⼈员-李岚,我们社区最近启动了⼀项「源创计划」,可以⼀站式同步您公众号的博客⽂章到OSCHINA账号上。
不知道您有没有兴趣参与「源创计划」,让更多开发者看到您的⽂章呢?
2020 年 11 月 14 日 15:23
回复
没有更多了
Java四种引用类型:强引用、软引用、弱引用、虚引用