一文读懂java中的Reference和引用类型

简介

java中有值类型也有引用类型,引用类型一般是针对于java中对象来说的,今天介绍一下java中的引用类型。java为引用类型专门定义了一个类叫做Reference。Reference是跟java垃圾回收机制息息相关的类,通过探讨Reference的实现可以更加深入的理解java的垃圾回收是怎么工作的。

本文先从java中的四种引用类型开始,一步一步揭开Reference的面纱。

java中的四种引用类型分别是:强引用,软引用,弱引用和虚引用。

强引用Strong Reference

java中的引用默认就是强引用,任何一个对象的赋值操作就产生了对这个对象的强引用。

我们看一个例子:

public class StrongReferenceUsage {

    @Test
public void stringReference(){
Object obj = new Object();
}
}

上面我们new了一个Object对象,并将其赋值给obj,这个obj就是new Object()的强引用。

强引用的特性是只要有强引用存在,被引用的对象就不会被垃圾回收。

软引用Soft Reference

软引用在java中有个专门的SoftReference类型,软引用的意思是只有在内存不足的情况下,被引用的对象才会被回收。

先看下SoftReference的定义:

public class SoftReference<T> extends Reference<T>

SoftReference继承自Reference。它有两种构造函数:

    public SoftReference(T referent)

和:

    public SoftReference(T referent, ReferenceQueue<? super T> q)

第一个参数很好理解,就是软引用的对象,第二个参数叫做ReferenceQueue,是用来存储封装的待回收Reference对象的,ReferenceQueue中的对象是由Reference类中的ReferenceHandler内部类进行处理的。

我们举个SoftReference的例子:

    @Test
public void softReference(){
Object obj = new Object();
SoftReference<Object> soft = new SoftReference<>(obj);
obj = null;
log.info("{}",soft.get());
System.gc();
log.info("{}",soft.get());
}

输出结果:

22:50:43.733 [main] INFO com.flydean.SoftReferenceUsage - java.lang.Object@71bc1ae4
22:50:43.749 [main] INFO com.flydean.SoftReferenceUsage - java.lang.Object@71bc1ae4

可以看到在内存充足的情况下,SoftReference引用的对象是不会被回收的。

弱引用weak Reference

weakReference和softReference很类似,不同的是weekReference引用的对象只要垃圾回收执行,就会被回收,而不管是否内存不足。

同样的WeakReference也有两个构造函数:

public WeakReference(T referent);

 public WeakReference(T referent, ReferenceQueue<? super T> q);

含义和SoftReference一致,这里就不再重复表述了。

我们看下弱引用的例子:

    @Test
public void weakReference() throws InterruptedException {
Object obj = new Object();
WeakReference<Object> weak = new WeakReference<>(obj);
obj = null;
log.info("{}",weak.get());
System.gc();
log.info("{}",weak.get());
}

输出结果:

22:58:02.019 [main] INFO com.flydean.WeakReferenceUsage - java.lang.Object@71bc1ae4
22:58:02.047 [main] INFO com.flydean.WeakReferenceUsage - null

我们看到gc过后,弱引用的对象被回收掉了。

虚引用PhantomReference

PhantomReference的作用是跟踪垃圾回收器收集对象的活动,在GC的过程中,如果发现有PhantomReference,GC则会将引用放到ReferenceQueue中,由程序员自己处理,当程序员调用ReferenceQueue.pull()方法,将引用出ReferenceQueue移除之后,Reference对象会变成Inactive状态,意味着被引用的对象可以被回收了。

和SoftReference和WeakReference不同的是,PhantomReference只有一个构造函数,必须传入ReferenceQueue:

public PhantomReference(T referent, ReferenceQueue<? super T> q)

看一个PhantomReference的例子:

@Slf4j
public class PhantomReferenceUsage { @Test
public void usePhantomReference(){
ReferenceQueue<Object> rq = new ReferenceQueue<>();
Object obj = new Object();
PhantomReference<Object> phantomReference = new PhantomReference<>(obj,rq);
obj = null;
log.info("{}",phantomReference.get());
System.gc();
Reference<Object> r = (Reference<Object>)rq.poll();
log.info("{}",r);
}
}

运行结果:

07:06:46.336 [main] INFO com.flydean.PhantomReferenceUsage - null
07:06:46.353 [main] INFO com.flydean.PhantomReferenceUsage - java.lang.ref.PhantomReference@136432db

我们看到get的值是null,而GC过后,poll是有值的。

因为PhantomReference引用的是需要被垃圾回收的对象,所以在类的定义中,get一直都是返回null:

    public T get() {
return null;
}

Reference和ReferenceQueue

讲完上面的四种引用,接下来我们谈一下他们的父类Reference和ReferenceQueue的作用。

Reference是一个抽象类,每个Reference都有一个指向的对象,在Reference中有5个非常重要的属性:referent,next,discovered,pending,queue。

private T referent;         /* Treated specially by GC */
volatile ReferenceQueue<? super T> queue;
Reference next;
transient private Reference<T> discovered; /* used by VM */
private static Reference<Object> pending = null;

每个Reference都可以看成是一个节点,多个Reference通过next,discovered和pending这三个属性进行关联。

先用一张图来对Reference有个整体的概念:

referent就是Reference实际引用的对象。

通过next属性,可以构建ReferenceQueue。

通过discovered属性,可以构建Discovered List。

通过pending属性,可以构建Pending List。

四大状态

在讲这三个Queue/List之前,我们先讲一下Reference的四个状态:

从上面的图中,我们可以看到一个Reference可以有四个状态。

因为Reference有两个构造函数,一个带ReferenceQueue,一个不带。


Reference(T referent) {
this(referent, null);
} Reference(T referent, ReferenceQueue<? super T> queue) {
this.referent = referent;
this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
}

对于带ReferenceQueue的Reference,GC会把要回收对象的Reference放到ReferenceQueue中,后续该Reference需要程序员自己处理(调用poll方法)。

不带ReferenceQueue的Reference,由GC自己处理,待回收的对象其Reference状态会变成Inactive。

创建好了Reference,就进入active状态。

active状态下,如果引用对象的可到达状态发送变化就会转变成Inactive或Pending状态。

Inactive状态很好理解,到达Inactive状态的Reference状态不能被改变,会等待GC回收。

Pending状态代表等待入Queue,Reference内部有个ReferenceHandler,会调用enqueue方法,将Pending对象入到Queue中。

入Queue的对象,其状态就变成了Enqueued。

Enqueued状态的对象,如果调用poll方法从ReferenceQueue拿出,则该Reference的状态就变成了Inactive,等待GC的回收。

这就是Reference的一个完整的生命周期。

三个Queue/List

有了上面四个状态的概念,我们接下来讲三个Queue/List:ReferenceQueue,discovered List和pending List。

ReferenceQueue在讲状态的时候已经讲过了,它本质是由Reference中的next连接而成的。用来存储GC待回收的对象。

pending List就是待入ReferenceQueue的list。

discovered List这个有点特别,在Pending状态时候,discovered List就等于pending List。

在Active状态的时候,discovered List实际上维持的是一个引用链。通过这个引用链,我们可以获得引用的链式结构,当某个Reference状态不再是Active状态时,需要将这个Reference从discovered List中删除。

WeakHashMap

最后讲一下WeakHashMap,WeakHashMap跟WeakReference有点类似,在WeakHashMap如果key不再被使用,被赋值为null的时候,该key对应的Entry会自动从WeakHashMap中删除。

我们举个例子:

    @Test
public void useWeakHashMap(){
WeakHashMap<Object, Object> map = new WeakHashMap<>();
Object key1= new Object();
Object value1= new Object();
Object key2= new Object();
Object value2= new Object(); map.put(key1, value1);
map.put(key2, value2);
log.info("{}",map); key1 = null;
System.gc();
log.info("{}",map); }

输出结果:

[main] INFO com.flydean.WeakHashMapUsage - {java.lang.Object@14899482=java.lang.Object@2437c6dc, java.lang.Object@11028347=java.lang.Object@1f89ab83}
[main] INFO com.flydean.WeakHashMapUsage - {java.lang.Object@14899482=java.lang.Object@2437c6dc}

可以看到gc过后,WeakHashMap只有一个Entry了。

总结

本文讲解了4个java中的引用类型,并深入探讨了Reference的内部机制,感兴趣的小伙伴可以留言一起讨论。

本文的例子https://github.com/ddean2009/learn-java-collections

欢迎关注我的公众号:程序那些事,更多精彩等着您!

更多内容请访问 www.flydean.com

一文读懂java中的Reference和引用类型的更多相关文章

  1. 一文读懂Java中的动态代理

    从代理模式说起 回顾前文: 设计模式系列之代理模式(Proxy Pattern) 要读懂动态代理,应从代理模式说起.而实现代理模式,常见有下面两种实现: (1) 代理类关联目标对象,实现目标对象实现的 ...

  2. 一文读懂Java动态代理

    作者 :潘潘 日期 :2020-11-22 事实上,对于很多Java编程人员来说,可能只需要达到从入门到上手的编程水准,就能很好的完成大部分研发工作.除非自己强主动获取,或者工作倒逼你学习,否则我们好 ...

  3. 一文读懂JAVA多线程

    背景渊源 摩尔定律 提到多线程好多书上都会提到摩尔定律,它是由英特尔创始人之一Gordon Moore提出来的.其内容为:当价格不变时,集成电路上可容纳的元器件的数目,约每隔18-24个月便会增加一倍 ...

  4. 读懂Java中的Socket编程

    Socket,又称为套接字,Socket是计算机网络通信的基本的技术之一.如今大多数基于网络的软件,如浏览器,即时通讯工具甚至是P2P下载都是基于Socket实现的.本文会介绍一下基于TCP/IP的S ...

  5. 读懂Java中的Socket编程(转)

    Socket,又称为套接字,Socket是计算机网络通信的基本的技术之一.如今大多数基于网络的软件,如浏览器,即时通讯工具甚至是P2P下载都是基于Socket实现的.本文会介绍一下基于TCP/IP的S ...

  6. 夯实Java基础系列7:一文读懂Java 代码块和执行顺序

    目录 Java中的构造方法 构造方法简介 构造方法实例 例 1 例 2 Java中的几种构造方法详解 普通构造方法 默认构造方法 重载构造方法 java子类构造方法调用父类构造方法 Java中的代码块 ...

  7. 夯实Java基础系列16:一文读懂Java IO流和常见面试题

    本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutorial 喜欢的话麻烦点下 ...

  8. 一文读懂Java类加载机制

    Java 类加载机制 Java 类加载机制详解. @pdai Java 类加载机制 类的生命周期 类的加载:查找并加载类的二进制数据 连接 验证:确保被加载的类的正确性 准备:为类的静态变量分配内存, ...

  9. 一文搞懂 Java 中的枚举,写得非常好!

    知识点 概念 enum的全称为 enumeration, 是 JDK 1.5 中引入的新特性. 在Java中,被 enum关键字修饰的类型就是枚举类型.形式如下: enum Color { RED, ...

  10. 一文搞懂--Java中重写equals方法为什么要重写hashcode方法?

    Java中重写equals方法为什么要重写hashcode方法? 直接看下面的例子: 首先我们只重写equals()方法 public class Test { public static void ...

随机推荐

  1. OpenCV开发笔记(六十):红胖子8分钟带你深入了解Harris角点检测(图文并茂+浅显易懂+程序源码)

    若该文为原创文章,未经允许不得转载原博主博客地址:https://blog.csdn.net/qq21497936原博主博客导航:https://blog.csdn.net/qq21497936/ar ...

  2. 【Azure 应用服务】如何查看App Service中的私网IP地址?

    问题描述 在使用App Service服务时,可以通过Azure 门户中的属性功能查看出站IP列表. 如果把App Service与虚拟网络(VNET)集成后,它就可以直接访问虚拟网络内部资源,那么如 ...

  3. vivo 在离线混部探索与实践

    作者:来自 vivo 互联网服务器团队 本文根据甘青.黄荣杰老师在"2023 vivo开发者大会"现场演讲内容整理而成. 伴随 vivo 互联网业务的高速发展,数据中心的规模不断扩 ...

  4. select 条件语句【GO 基础】

    〇.select 简介 select 语句类似于 switch 语句,但是 select 会随机执行一个可运行的 case.如果没有 case 可运行,它将阻塞,直到有 case 可运行. selec ...

  5. mysql中innodb创建表的一些限制

    1. 背景 在新创建mysql数据表的时候.不太确定表能创建多少个字段,多少个索引.索引多少有限制么?mysql的数据是怎么存储的存在在哪里. 2.基本个数限制 在MySQL5.6.9以后的版本,一个 ...

  6. 关于Chrome版本太旧的更新问题

    •问题 这两天不知道咋了,Chrome 老是给我提示版本太旧,需要更新. 作为一名资深的强迫症患者,这让我很是不爽. •解决方案 在桌面找到 Chrome 图标,右击选择[属性],在该位置添加如下语句 ...

  7. java线程示例

    需要开启线程 的方法继承线程类,并在run  中写逻辑 public class Ant extends Thread{ Cake cake; public Ant(String name,Cake ...

  8. tomcat startup.bat 包含springboot的输出 里面乱码的解决方案

    springboot输出是用的 logger 它的编码是 UTF-8 tomcat 默认也是UTF-8 但是win10 默认的 命令窗口是 GBK的,如果把tomcat 和 springboot的格式 ...

  9. 将谷歌chrome浏览器主题变黑的方法

    两个步骤: 第一: 桌面找到google chrome图标右键->属性,在后面加上: --force-dark-mode (注意有空格) 第二: 1.浏览器地址输入chrome://flags/ ...

  10. day06-SpringMVC底层机制简单实现-02

    SpringMVC底层机制简单实现-02 https://github.com/liyuelian/springmvc-demo.git 4.任务3-从web.xml动态获取容器配置文件 4.1分析 ...