Java 源码剖析——彻底搞懂 Reference 和 ReferenceQueue

本贴最后更新于 2299 天前,其中的信息可能已经时移世改

之前博主的一篇读书笔记——《深入理解 Java 虚拟机》系列之回收对象算法与四种引用类型博客中为大家介绍了 Java 中的四种引用类型,很多同学都希望能够对引用,还有不同类型引用的原理进行更深入的了解。因此博主查看了抽象父类 Reference 和负责注册引用对象的引用队列 ReferenceQueue 的源码,在此和大家一起分享,并做了一些分析,感兴趣的同学可以一起学习。

Reference 源码分析

首先我们先看一下 Reference 类的注释:

/**
 * Abstract base class for reference objects.  This class defines the
 * operations common to all reference objects.  Because reference objects are
 * implemented in close cooperation with the garbage collector, this class may
 * not be subclassed directly.
 引用对象的抽象基类。此类定义了常用于所有引用对象的操作。因为引用对象是通过与垃圾回收器的密切合作来实现的,所以不能直接为此类创建子类。
 */

该类提供了两个构造函数:

Reference(T referent) {
	this(referent, null);
}

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

一个构造函数带需要注册到的引用队列,一个不带。带 queue 的意义在于我们可以吃从外部通过对 queue 的操作来了解到引用实例所指向的实际对象是否被回收了,同时我们也可以通过 queue 对引用实例进行一些额外的操作;但如果我们的引用实例在创建时没有指定一个引用队列,那我们要想知道实际对象是否被回收,就只能够不停地轮询引用实例的 get()方法是否为空了。值得注意的是虚引用 PhantomReference,由于它的 get()方法永远返回 null,因此它的构造函数必须指定一个引用队列。这两种查询实际对象是否被回收的方法都有应用,如 weakHashMap 中就选择去查询 queue 的数据,来判定是否有对象将被回收;而 ThreadLocalMap,则采用判断 get()是否为 null 来作处理。

接下来是它的主要成员:

private T referent;         /* Treated specially by GC */

在这里我们首先明确一些名词,Reference 类也被称为引用类,它的实例 Reference Instance 就是引用实例,但是由于它是一个抽象类,它的实例只能是子类软(soft)引用,弱(weak)引用,虚(phantom)引用中的某个,至于引用实例所引用的对象我们称之为实际对象(也就是我们上面所写出的 referent)。

volatile ReferenceQueue<? super T> queue;   /* 引用对象队列*/

queue 是当前引用实例所注册的引用队列,一旦实际对象的可达性发生适当的变化后,此引用实例将会被添加到 queue 中。

/* When active:   NULL
 *     pending:   this
 *    Enqueued:   next reference in queue (or this if last)
 *    Inactive:   this
 */
@SuppressWarnings("rawtypes")
Reference next;

next 用来表示当前引用实例的下一个需要被处理的引用实例,我们在注释中看到的四个状态,是引用实例的内部状态,不可以被外部查看或是直接修改:

  • Active:新创建的引用实例处于 Active 状态,但当 GC 检测到该实例引用的实际对象的可达性发生某些适当的改变(实际对象对于 GC roots 不可达)后,它的状态将会根据此实例是否注册在引用队列中而变成 Pending 或是 Inactive。
  • Pending:当引用实例被放置在 pending-Reference list 中时,它处于 Pending 状态。此时,该实例在等待一个叫 Reference-handler 的线程将此实例进行 enqueue 操作。如果某个引用实例没有注册在一个引用队列中,该实例将永远不会进入 Pending 状态。
  • Enqueued: 当引用实例被添加到它注册在的引用队列中时,该实例处于 Enqueued 状态。当某个引用实例被从引用队列中删除后,该实例将从 Enqueued 状态变为 Inactive 状态。如果某个引用实例没有注册在一个引用队列中,该实例将永远不会进入 Enqueued 状态。
  • Inactive:一旦某个引用实例处于 Inactive 状态,它的状态将不再会发生改变,同时说明该引用实例所指向的实际对象一定会被 GC 所回收。

事实上 Reference 类并没有显示地定义内部状态值,JVM 仅需要通过成员 queue 和 next 的值就可以判断当前引用实例处于哪个状态:

  • Active:queue 为创建引用实例时传入的 ReferenceQueue 的实例或是 ReferenceQueue.NULL;next 为 null
  • Pending:queue 为创建引用实例时传入的 ReferenceQueue 的实例;next 为 this
  • Enqueued:queue 为 ReferenceQueue.ENQUEUED;next 为队列中下一个需要被处理的实例或是 this 如果该实例为队列中的最后一个
  • Inactive:queue 为 ReferenceQueue.NULL;next 为 this
/* List of References waiting to be enqueued.  The collector adds
 * References to this list, while the Reference-handler thread removes
 * them.  This list is protected by the above lock object. The
 * list uses the discovered field to link its elements.
 */
private static Reference<Object> pending = null;

/* When active:   next element in a discovered reference list maintained by GC (or this if last)
 *     pending:   next element in the pending list (or null if last)
 *   otherwise:   NULL
 */
transient private Reference<T> discovered;  /* used by VM */

看到注释的同学们有可能会有一些疑惑,明明 pending 是一个 Reference 类型的对象,为什么注释说它是一个 list 呢?其实是因为 GC 检测到某个引用实例指向的实际对象不可达后,会将该 pending 指向该引用实例,discovered 字段则是用来表示下一个需要被处理的实例,因此我们只要不断地在处理完当前 pending 之后,将 discovered 指向的实例赋予给 pending 即可。所以这个 static 字段 pending 其实就是一个链表。

private static class ReferenceHandler extends Thread {
  ......
  public void run() {
	  while (true) {
		  tryHandlePending(true);
	  }
  }
}

ReferenceHandler 是一个优先级最高的线程,它执行的工作就是将 pending list 中的引用实例添加到引用队列中,并将 pending 指向下一个引用实例。

 static boolean tryHandlePending(boolean waitForNotify) {
	 ......
	 synchronized (lock) {
		if (pending != null) {
			r = pending;
			// 'instanceof' might throw OutOfMemoryError sometimes
			// so do this before un-linking 'r' from the 'pending' chain...
			c = r instanceof Cleaner ? (Cleaner) r : null;
			// unlink 'r' from 'pending' chain
			pending = r.discovered;
			r.discovered = null;
		}
	}
	......
	ReferenceQueue<? super Object> q = r.queue;
        if (q != ReferenceQueue.NULL) q.enqueue(r);
        return true;
 }

Reference 对外提供的方法就比较简单了:

public T get() {
   return this.referent;
}

get()方法就是简单的返回引用实例所引用的实际对象,如果该对象被回收了或者该引用实例被 clear 了则返回 null

public void clear() {
  this.referent = null;
}

调用此方法不会导致此对象入队。此方法仅由 Java 代码调用;当垃圾收集器清除引用时,它直接执行,而不调用此方法。
clear 的方法本质上就是将 referent 置为 null,清除引用实例所引用的实际对象,这样通过 get()方法就不能再访问到实际对象了。

public boolean isEnqueued() {
  return (this.queue == ReferenceQueue.ENQUEUED);
}

判断此引用实例是否已经被放入队列中是通过引用队列实例是否等于 ReferenceQueue.ENQUEUED 来得知的。

public boolean enqueue() {
  return this.queue.enqueue(this);
}

enqueue()方法能够手动将引用实例加入到引用队列当中去。

ReferenceQueue 源码分析

同样我们先看一下 ReferenceQueue 的注释:

/**
 * Reference queues, to which registered reference objects are appended by the
 * garbage collector after the appropriate reachability changes are detected.
 * 引用队列,在检测到适当的可到达性更改后,垃圾回收器将已注册的引用对象添加到该队列中
 */

ReferenceQueue 实现了队列的入队(enqueue)和出队(poll),其中的内部元素就是我们上文中提到的 Reference 对象。队列元素的存储结构是单链式存储,依靠每个 reference 对象的 next 域去找下一个元素。

主要成员有:

private  volatile Reference extends T> head = null;

用来存储当前需要被处理的节点

static ReferenceQueue NULL = new Null<>();
static ReferenceQueue ENQUEUED = new Null<>();

static 变量 NUlL 和 ENQUEUED 分别用来表示没有提供默认引用队列的空队列和已经执行过 enqueue 操作的队列。

引用实例入队的逻辑很简单:

synchronized (lock) {
	// 检查reference是否已经执行过入队操作
	ReferenceQueue<?> queue = r.queue;
	if ((queue == NULL) || (queue == ENQUEUED)) {
		return false;
	}
	//将引用实例的成员queue置为ENQUEUED
	r.queue = ENQUEUED;
	//若头节点为空,说明该引用实例为队列中的第一个元素,将它的next实例等于this
	//若头节点不为空,将它的next实例指向头节点指向的元素
	r.next = (head == null) ? r : head;
	//头节点指向当前引用实例
	head = r;
	//length+1
	queueLength++;

	lock.notifyAll();
	return true;
}

简单来说,入队操作就是将每次需要入队的引用实例放在头节点的位置,并将它的 next 域指向旧的头节点元素。因此整个 ReferenceQueue 是一个后进先出的数据结构。

出队的逻辑为:

r指向头节点元素
Reference<? extends T> r = head;
if (r != null) {
	//头节点指向null,如果队列中只有一个元素;否则指向r.next
	head = (r.next == r) ? null : r.next; 
	//头节点元素的queue指向ReferenceQueue.NULL
	r.queue = NULL;
	//将r.next指向this
	r.next = r;
	//length-1
	queueLength--;
	
	return r;
}

总体来看,ReferenceQueue 的作用就是 JAVA GC 与 Reference 引用对象之间的中间层,我们可以在外部通过 ReferenceQueue 及时地根据所监听的对象的可达性状态变化而采取处理操作。

  • B3log

    B3log 是一个开源组织,名字来源于“Bulletin Board Blog”缩写,目标是将独立博客与论坛结合,形成一种新的网络社区体验,详细请看 B3log 构思。目前 B3log 已经开源了多款产品:SymSoloVditor思源笔记

    1090 引用 • 3467 回帖 • 298 关注
  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3165 引用 • 8206 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...

推荐标签 标签

  • 以太坊

    以太坊(Ethereum)并不是一个机构,而是一款能够在区块链上实现智能合约、开源的底层系统。以太坊是一个平台和一种编程语言 Solidity,使开发人员能够建立和发布下一代去中心化应用。 以太坊可以用来编程、分散、担保和交易任何事物:投票、域名、金融交易所、众筹、公司管理、合同和知识产权等等。

    34 引用 • 367 回帖 • 2 关注
  • H2

    H2 是一个开源的嵌入式数据库引擎,采用 Java 语言编写,不受平台的限制,同时 H2 提供了一个十分方便的 web 控制台用于操作和管理数据库内容。H2 还提供兼容模式,可以兼容一些主流的数据库,因此采用 H2 作为开发期的数据库非常方便。

    11 引用 • 54 回帖 • 638 关注
  • Kotlin

    Kotlin 是一种在 Java 虚拟机上运行的静态类型编程语言,由 JetBrains 设计开发并开源。Kotlin 可以编译成 Java 字节码,也可以编译成 JavaScript,方便在没有 JVM 的设备上运行。在 Google I/O 2017 中,Google 宣布 Kotlin 成为 Android 官方开发语言。

    19 引用 • 33 回帖 • 21 关注
  • Python

    Python 是一种面向对象、直译式电脑编程语言,具有近二十年的发展历史,成熟且稳定。它包含了一组完善而且容易理解的标准库,能够轻松完成很多常见的任务。它的语法简捷和清晰,尽量使用无异义的英语单词,与其它大多数程序设计语言使用大括号不一样,它使用缩进来定义语句块。

    534 引用 • 671 回帖
  • Webswing

    Webswing 是一个能将任何 Swing 应用通过纯 HTML5 运行在浏览器中的 Web 服务器,详细介绍请看 将 Java Swing 应用变成 Web 应用

    1 引用 • 15 回帖 • 635 关注
  • NGINX

    NGINX 是一个高性能的 HTTP 和反向代理服务器,也是一个 IMAP/POP3/SMTP 代理服务器。 NGINX 是由 Igor Sysoev 为俄罗斯访问量第二的 Rambler.ru 站点开发的,第一个公开版本 0.1.0 发布于 2004 年 10 月 4 日。

    311 引用 • 546 回帖 • 56 关注
  • HBase

    HBase 是一个分布式的、面向列的开源数据库,该技术来源于 Fay Chang 所撰写的 Google 论文 “Bigtable:一个结构化数据的分布式存储系统”。就像 Bigtable 利用了 Google 文件系统所提供的分布式数据存储一样,HBase 在 Hadoop 之上提供了类似于 Bigtable 的能力。

    17 引用 • 6 回帖 • 32 关注
  • WordPress

    WordPress 是一个使用 PHP 语言开发的博客平台,用户可以在支持 PHP 和 MySQL 数据库的服务器上架设自己的博客。也可以把 WordPress 当作一个内容管理系统(CMS)来使用。WordPress 是一个免费的开源项目,在 GNU 通用公共许可证(GPLv2)下授权发布。

    45 引用 • 113 回帖 • 321 关注
  • JVM

    JVM(Java Virtual Machine)Java 虚拟机是一个微型操作系统,有自己的硬件构架体系,还有相应的指令系统。能够识别 Java 独特的 .class 文件(字节码),能够将这些文件中的信息读取出来,使得 Java 程序只需要生成 Java 虚拟机上的字节码后就能在不同操作系统平台上进行运行。

    180 引用 • 120 回帖 • 2 关注
  • 心情

    心是产生任何想法的源泉,心本体会陷入到对自己本体不能理解的状态中,因为心能产生任何想法,不能分出对错,不能分出自己。

    59 引用 • 369 回帖
  • Quicker

    Quicker 您的指尖工具箱!操作更少,收获更多!

    16 引用 • 68 回帖
  • 架构

    我们平时所说的“架构”主要是指软件架构,这是有关软件整体结构与组件的抽象描述,用于指导软件系统各个方面的设计。另外还有“业务架构”、“网络架构”、“硬件架构”等细分领域。

    139 引用 • 441 回帖
  • Hprose

    Hprose 是一款先进的轻量级、跨语言、跨平台、无侵入式、高性能动态远程对象调用引擎库。它不仅简单易用,而且功能强大。你无需专门学习,只需看上几眼,就能用它轻松构建分布式应用系统。

    9 引用 • 17 回帖 • 591 关注
  • RIP

    愿逝者安息!

    8 引用 • 92 回帖 • 285 关注
  • 招聘

    哪里都缺人,哪里都不缺人。

    189 引用 • 1056 回帖
  • FlowUs

    FlowUs.息流 个人及团队的新一代生产力工具。

    让复杂的信息管理更轻松、自由、充满创意。

    1 引用 • 3 关注
  • Rust

    Rust 是一门赋予每个人构建可靠且高效软件能力的语言。Rust 由 Mozilla 开发,最早发布于 2014 年 9 月。

    57 引用 • 22 回帖
  • 数据库

    据说 99% 的性能瓶颈都在数据库。

    330 引用 • 614 回帖
  • Love2D

    Love2D 是一个开源的, 跨平台的 2D 游戏引擎。使用纯 Lua 脚本来进行游戏开发。目前支持的平台有 Windows, Mac OS X, Linux, Android 和 iOS。

    14 引用 • 53 回帖 • 506 关注
  • Electron

    Electron 基于 Chromium 和 Node.js,让你可以使用 HTML、CSS 和 JavaScript 构建应用。它是一个由 GitHub 及众多贡献者组成的活跃社区共同维护的开源项目,兼容 Mac、Windows 和 Linux,它构建的应用可在这三个操作系统上面运行。

    15 引用 • 136 回帖
  • CSDN

    CSDN (Chinese Software Developer Network) 创立于 1999 年,是中国的 IT 社区和服务平台,为中国的软件开发者和 IT 从业者提供知识传播、职业发展、软件开发等全生命周期服务,满足他们在职业发展中学习及共享知识和信息、建立职业发展社交圈、通过软件开发实现技术商业化等刚性需求。

    14 引用 • 155 回帖
  • 运维

    互联网运维工作,以服务为中心,以稳定、安全、高效为三个基本点,确保公司的互联网业务能够 7×24 小时为用户提供高质量的服务。

    148 引用 • 257 回帖 • 1 关注
  • JetBrains

    JetBrains 是一家捷克的软件开发公司,该公司位于捷克的布拉格,并在俄国的圣彼得堡及美国麻州波士顿都设有办公室,该公司最为人所熟知的产品是 Java 编程语言开发撰写时所用的集成开发环境:IntelliJ IDEA

    18 引用 • 54 回帖
  • IDEA

    IDEA 全称 IntelliJ IDEA,是一款 Java 语言开发的集成环境,在业界被公认为最好的 Java 开发工具之一。IDEA 是 JetBrains 公司的产品,这家公司总部位于捷克共和国的首都布拉格,开发人员以严谨著称的东欧程序员为主。

    180 引用 • 400 回帖
  • GitBook

    GitBook 使您的团队可以轻松编写和维护高质量的文档。 分享知识,提高团队的工作效率,让用户满意。

    3 引用 • 8 回帖 • 1 关注
  • SendCloud

    SendCloud 由搜狐武汉研发中心孵化的项目,是致力于为开发者提供高质量的触发邮件服务的云端邮件发送平台,为开发者提供便利的 API 接口来调用服务,让邮件准确迅速到达用户收件箱并获得强大的追踪数据。

    2 引用 • 8 回帖 • 429 关注
  • CodeMirror
    1 引用 • 2 回帖 • 109 关注