一、概述

  为了更好的理解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. display的理解

    display可把框内显示的内容改变(自我理解) none 此元素不会被显示. block 此元素将显示为块级元素,此元素前后会带有换行符. inline 默认.此元素会被显示为内联元素,元素前后没有 ...

  2. VS2012 easyui datagrid url访问之坑

    VS2012 easyui datagrid url访问之坑 url属性放的是地址的话 返回的json格式必须有 total 和 rows,如下: {"total":2," ...

  3. Liferay7 BPM门户开发之46: 集成Activiti用户、用户组、成员关系同步

    在实际的BPM集成开发过程中,Liferay和Activiti这两个异构的系统之间,用户.组的同步需求非常重要,用来实现签收组的概念,比如指定签收组.会签.抢签都需要用到. Activiti可以通过自 ...

  4. CCNA基础 IP地址子网划分

    计算机是一个非常神奇的物品,它的核心算法是凌驾于任何代码架构.然而互联网网络( Internat )作为整个生态的基础资源.什么?你还不会子网划分? 没关系,看到子网掩码不要怕.因为它无非就是问你 & ...

  5. android rectF

    new Rect(left , top, right , bottom) 这个构造方法需要四个参数这四个参数 指明了什么位置 ?我们就来解释怎么画 这个 矩形 这四个 参数 分别代表的意思是:left ...

  6. JSP开发环境配置问题解答

    有过JSP开发经验的同学对于JSP开发环境的配置一定非常的很有感触,十分的繁琐,有时因为一个小的问题导致我们配置的配置前功尽弃,本篇我将重点带领大家一起探讨一下关于JSP环境配置的一些常见问题,及解决 ...

  7. 借助 CSS Colorguard 来避免使用重复的颜色

    每一个 CSS 项目开始时愿望都是很美好的,但不可避免地,多人协作的项目相互之间可能重复使用了相似的颜色,而你从来不知道它们的存在.CSS Colorguard 帮助您保持您想要的颜色设置,当你添加的 ...

  8. JavaScript中,提取子字符串方法:Slice、Substring、Substr的比较。

    在JavaScript中,提取子字符串主要是通过 Slice.Substring.Substr 三个方法之一. // slice // 语法: string.slice(beginSlice [, e ...

  9. jsonp跨域问题

    JSONP是一个非官方的协议,它允许在服务器端集成Script tags返回至客户端,通过javascript callback的形式实现跨域访问(这仅仅是JSONP简单的实现形式). 同源策略限制 ...

  10. T-SQL---多值模糊查询的处理

    多值模糊查询的处理 所谓多值模糊查询,就是应用程序中传递过来多个参数,对这些参数做拆分,拆分之后,对拆分结果的key值分别做模糊查询处理 对于精确匹配时,不管是单个Key值还是多个Key值,都很容易处 ...