一、概述

  为了更好的理解WeakHashMap的原理,我们有必要先来了解一下WeakReference的作用及实现原理。Java中有一个专门的包java.lang.ref,里面定义了我们通常所说的几种引用,具体来说如下:

Reference: 基础的引用类,是一个抽象类,定义了引用的一些基本方法

SoftReference: 软引用,软引用对象在应用出现OOM之前会被回收。

WeakReference: 弱引用,如果一个对象只被弱引用关联,则垃圾回收器会回收它。

PhantomReference:幻引用,这类用得比较少,个人也不太理解,应该是属于引用级别最弱的引用。

  ReferenceQueue: 引用队列,垃圾回收器会将已回收的队象放到这个队列里。

除了上述的三类引用之外,还有一类引用叫做强引用,强引用就是我们通常所说的引用,所以这里java并没有单独定义一个引用类来表示。

本文的重点是介绍WeakReference的使用,及其实现原理。

二、WeakReference的使用示例

  在介绍其实现原理前,我们先来看一下它的使用效果,根据WeakReference的定义, 如果一个对象只被弱引用所引用,那么这个对象就是弱可达的,弱可达对象会在系统进行垃圾回收时被回收。

  可能这样说还是不好理解,所以我们以一个示例来说明其使用的效果,代码如下:

public class TestWeakReference {
public static void main(String[] args) throws Exception{ UserInfo userInfo = new UserInfo("Jim Green");
UserInfo anotherUser = userInfo;
WeakReference<UserInfo> weakUser = new WeakReference<>(userInfo); System.out.println("\nBefore userInfo is null");
System.out.println("strong ref:" + anotherUser);
System.out.println("weak ref:" + weakUser.get()); userInfo = null;
System.gc(); System.out.println("\nAfter userInfo is null");
System.out.println("strong ref:" + anotherUser);
System.out.println("weak ref:" + weakUser.get()); anotherUser = new UserInfo("Jim White");
System.gc();
System.out.println("\nAfter anotherUser is changed");
System.out.println("strong ref:" + anotherUser);
System.out.println("weak ref:" + weakUser.get()); }
} class UserInfo {
private String name; public UserInfo(String name){
this.name = name;
} @Override
public String toString() {
return "Name is " + name;
}
}

上述代码的运行结果如下:

从上面的结果可以看到,刚开始,这三个引用都指向同一个用户对象Jim Green, 然后我们将原引用置为null,进行垃圾回收,但由于还有一个强引用指向这个Jim Green, 所以这个用户对象不会被回收,最后,当没有强引用再指向它了,再做垃圾回收,则Jim Green被回收了。

这个结果也印证了WeakReference的作用,如果其指向的对象没有被任何强引用指向,则该对象是可以回收的。

下面我们再以图例的形式来演示这个过程。

三、WeakReference 的实现原理

  上面我们详细演示了使用的方式,接下来我们再说一下其主要实现。

1. UML图

WeakReference , Reference和ReferenceQueue三者之间的关系如下:

  上图中列出了这三个类的一些主要的属性和方法。其中WeakReference是抽象类,但是提供了一个引用所需要的基本功能,而WeakReference则只是简单继承,并没有实现任何扩展的功能。

  Reference有两个构造的方法,分别是带队列和不带队列的,如下:

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

  可见,referent属性存储了其所引用的对象,而queue这个字段是可选的,前面说到,queue的作用保存对象将被回收的引用,由垃圾回收器负责往里面添加,但如果不提供,则没有这一过程。

  2. 状态说明:

  根据类的说明,引用有四种状态:

  Active: 这是创建该引用后的初始状态,如果该引用对象所引用的对象的可达性发生了变化,则引用本身的状态将变为Pending或Inactive,取决于这个引用在创建时是否使用了队列。如果使用了队列,则状态将会转化为Pending,否则直接变为Inactive。

  Pending: 由Active转化而来,处于该状态的引用在一个公共的pending队列里,等待被添加到queue里去。对于pending队列的处理由一个优先级比较高的守护线程实时监控处理。如果该引用在初始化时没有使用queue,则不会被加到pending队列里,当然也不可能添加到queue里。

  Enqueued: 由Pending转化而来,如果pending列表中的引用被正确添加到了queue里,则引用的状态为enqueued,如果该引用被从queue队列里移除了,则其将变为InActive状态。同样,这个前提就是构造时需要指定queue.

  InActive: 最后的状态,处于这个状态的引用对象实际上已经没什么作用了,状态也不会发生任何改变。

  这四种状态只是一种说明,实际上Reference对象并没有任何的status字段,不过作为队列中的节点,它有一个next字段,当状态为Active时,其next为null,而当其为其它状态时,next一定不为null,而是指向队列中的下一个引用,如果其本身就是队列中的最后一个元素,则next指向其自身。

  3. 原理说明:

  在介绍了结构和状态说明后,我们再来对其实现进行分析,这个需要分两种情况:

  1)构造时没有使用queue:

  这种情况比较简单,状态转换只有Active和InActive,不涉及到队列的操作,当引用所指向的对象没有任何其它的强引用时,垃圾回收器将会回收该对象,而其状态也应该会变为InActive.值得注意的时,这样get()就会直接返回null了。

  2)构造函数使用了queue:

  这种情况就复杂了,当引用指向的对象没有其它的强引用时,垃圾回收器会先将其添加到pending队列里,而Reference会通过一个公共的守护线程来处理pending队列里的引用对象,将其添加到queue队列中去。

  这个过程的处理逻辑如下:

 private static class ReferenceHandler extends Thread {

         ReferenceHandler(ThreadGroup g, String name) {
super(g, name);
} public void run() {
for (;;) { Reference r;
synchronized (lock) {
if (pending != null) {
r = pending; //取一个元素
Reference rn = r.next;//找队列中下一个引用对象
pending = (rn == r) ? null : rn; //如果是最后一个元素,则pending队列置空
r.next = r; //改变其next
} else {
try {
lock.wait(); //等待
} catch (InterruptedException x) { }
continue;
}
} // Fast path for cleaners
if (r instanceof Cleaner) {
((Cleaner)r).clean();
continue;
} ReferenceQueue q = r.queue;
if (q != ReferenceQueue.NULL) q.enqueue(r);//调用队列中的入队方法
}
}
}

  上面的方法会一直处理pending队列直到为null,之后将处于wait状态,那么问题来了,当pending队列再次不为空时,这个线程需要被唤醒。往pending队列里加引用对象,并执行唤醒操作的工作是谁来完成的呢?答案是由垃圾回收器在回收引用指向的对象时来调用的

  所以说,是垃圾回收器完成了引用对象从Active到Pending的转换,而引用对象的线程完成了引用对象由Pending到Enqueued的转换。

  接下来我们再了解下ReferenceQueue中入队的处理过程:

 boolean enqueue(Reference<? extends T> r) { /* Called only by Reference class */
synchronized (r) {
if (r.queue == ENQUEUED) return false;
synchronized (lock) {
r.queue = ENQUEUED;
r.next = (head == null) ? r : head;
head = r;
queueLength++;
if (r instanceof FinalReference) {
sun.misc.VM.addFinalRefCount(1);
}
lock.notifyAll();
return true;
}
}
}

 从其实现逻辑可以看出,每次入队是从头入队,入队后,更新其queue属性,这样可以防止多次入队。

对应还有出队的功能,这个就不再分析了。

  queue的入队我们介绍完了,但是垃圾回收器不会对这个队列做出队操作,那么这个队列有什么用呢?JDK中有一段对其的描述如下:

  “在创建引用对象时,通过向 引用队列 注册 一个适当的引用对象,程序可以请求在对象可到达性更改时获得通知。在垃圾回收器确定引用的可到达性已经更改为对应于引用类型的值之后的某一时间,它会将引用添加到相关的队列中。此时,该引用被认为是 已加入队列的。通过轮询或阻塞,直到获得了引用,程序才可以从队列中移除引用。引用队列是通过 ReferenceQueue 类实现的。”

  所以,这个queue是提供给应用程序通知用的,也就是说,程序可以通过监听这个队列,来获悉哪些弱引用所指向的对象已经被回收了,进而程序可以做相应的处理。至于如何监控,可以参考Reference类中对于pending队列的处理方式。

四、总结

  至此,我们对Reference及ReferenceQueue的实现方式做了一个完整的介绍,下面再总结一下:

  1. WeakReference在创建时需要关联到另一个对象,如果该对象没有别的普通(强)引用,则该对象将会被垃圾回收器回收。

  2. Reference在定义时可以指定一个类型为ReferenceQueue的队列,该队列的作用则是存储那些关联对象已经被回收了的Reference对象,以供应用程序监听,但如何处理由应用程序自己决定。

  3. WeakReference继承于Reference,但自身并未提供扩展的功能。

基础-WeakReference的更多相关文章

  1. java基础回顾(六)——WeakReference、SoftReference

    在Java里, 当一个对象o被创建时, 它被放在Heap里. 当GC运行的时候, 如果发现没有任何引用指向o, o就会被回收以腾出内存空间. 或者换句话说, 一个对象被回收, 必须满足两个条件: 1) ...

  2. C#基础知识回顾:1.由WeakReference想到对象的创建与销毁

    .Net Framework中,把资源分为托管资源和非托管资源两大类, 托管资源指可以通过.Net Frame垃圾回收器进行回收的资源,主要是指分配在托管堆上你的内存资源,这类资源的回收是不需要人工干 ...

  3. Java基础加强之多线程篇(线程创建与终止、互斥、通信、本地变量)

    线程创建与终止 线程创建 Thread类与Runnable接口的关系 public interface Runnable { public abstract void run(); } public ...

  4. JAVA基础整理-集合篇(一)

    集合作为JAVA的基础知识,本来感觉自己理解的很清楚了,但是在最近的一次面试中还是答得不尽如人意!再次做一下整理,以便加深理解以及随时查阅. 首先,java.util包中三个重要的接口及特点:List ...

  5. java基础语法要点<一>(基于1.8)

    http://yishouce.com/java/run http://www.shucunwang.com/RunCode/java/ 数据类型 8种基本数据类型及对应的 类型封装器 byte, s ...

  6. 基础4 Android基础

    基础4 Android基础 1. Activity与Fragment的生命周期. Activity生命周期 打开应用 onCreate()->onStart()->onResume 按BA ...

  7. 基础1 JavaSe基础

    JavaSe基础 1. 九种基本数据类型的大小,以及他们的封装类 boolean 无明确指定 Boolean char 16bits Character byte 8bits Byte short 1 ...

  8. JVM基础(5)-垃圾回收机制

    一.对象引用的类型 Java 中的垃圾回收一般是在 Java 堆中进行,因为堆中几乎存放了 Java 中所有的对象实例.谈到 Java 堆中的垃圾回收,自然要谈到引用.在 JDK1.2 之前,Java ...

  9. java基础解析系列(七)---ThreadLocal原理分析

    java基础解析系列(七)---ThreadLocal原理分析 目录 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系列(二)-- ...

随机推荐

  1. Atitit.编译原理与概论

    Atitit.编译原理与概论 编译原理 词法分析 Ast构建,语法分析 语意分析 6 数据结构  1. ▪ 记号 2. ▪ 语法树 3. ▪ 符号表 4. ▪ 常数表 5. ▪ 中间代码 1. ▪ 临 ...

  2. salesforce 零基础开发入门学习(九)Approval Process 介绍

    在阅读此篇文章前,可以先参考阅读一个前辈总结的关于Approval Process的操作.以下为参考的链接: http://www.cnblogs.com/mingmingruyuedlut/p/37 ...

  3. WPF入门教程系列十三——依赖属性(三)

    四. 只读依赖属性 在以前在对于非WPF的功能来说,对于类的属性的封装中,经常会对那些希望暴露给外界只读操作的字段封装成只读属性,同样在WPF中也提供了只读属性的概念,如一些 WPF控件的依赖属性是只 ...

  4. 快速入门系列--CLR--03泛型集合

    .NET中的泛型集合 在这里主要介绍常见的泛型集合,很多时候其并发时的线程安全性常常令我们担忧.因而简述下.NET并发时线程安全特性,其详情请见MSDN. 普通集合都不支持多重并发写操作 部分支持单线 ...

  5. ASP.NET中使用DataGrid控件按照条件显示GridView单元格的颜色

    问题描述: 我在做一个关于信用卡管理系统时遇到一个问题:信用卡内金额低于100元时,数字颜色显示为红色,其余显示为绿色 之前,尝试了修改成为模板列以及转换成Reapeater控件,甚至用了Jquery ...

  6. Topology Shapes of OpenCascade BRep

    Topology Shapes of OpenCascade BRep eryar@163.com 摘要Abstract:通过对OpenCascade中的BRep数据的读写,理解边界表示法的概念及实现 ...

  7. HTML的16个全局属性

    前面的话 在HTML中,属性能表达相当丰富的语义,而且属性也会额外提供很多实用的功能,HTML共支持16个常见的全局属性. HTML原有属性 accesskey 作用:浏览器用来创建激活或聚焦元素的快 ...

  8. JAVA设计模式《四》

    经过前几篇的介绍相信大家对JAVA的设计模式一定有所解了,本篇我们再一起学习一下适配器模式.代理模式和工厂模式. 适配器模式使用的场景非常多,例如现实生活中,我们的笔记本电脑的充电线大部分都是三向插头 ...

  9. Spring MVC 学习总结(二)——控制器定义与@RequestMapping详解

    一.控制器定义 控制器提供访问应用程序的行为,通常通过服务接口定义或注解定义两种方法实现. 控制器解析用户的请求并将其转换为一个模型.在Spring MVC中一个控制器可以包含多个Action(动作. ...

  10. Primer – 支撑 GitHub 的 CSS 工具包和准则

    Primer 是一个 CSS 工具包,支撑着 GitHub 的前端设计.它的目的仅限于提供通用部件,为我们的开发者提供最大的灵活性,并保持 GitHub 的独特风格.它基于 SCSS 建成,可以通过 ...