基础-WeakReference
一、概述
为了更好的理解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的更多相关文章
- java基础回顾(六)——WeakReference、SoftReference
在Java里, 当一个对象o被创建时, 它被放在Heap里. 当GC运行的时候, 如果发现没有任何引用指向o, o就会被回收以腾出内存空间. 或者换句话说, 一个对象被回收, 必须满足两个条件: 1) ...
- C#基础知识回顾:1.由WeakReference想到对象的创建与销毁
.Net Framework中,把资源分为托管资源和非托管资源两大类, 托管资源指可以通过.Net Frame垃圾回收器进行回收的资源,主要是指分配在托管堆上你的内存资源,这类资源的回收是不需要人工干 ...
- Java基础加强之多线程篇(线程创建与终止、互斥、通信、本地变量)
线程创建与终止 线程创建 Thread类与Runnable接口的关系 public interface Runnable { public abstract void run(); } public ...
- JAVA基础整理-集合篇(一)
集合作为JAVA的基础知识,本来感觉自己理解的很清楚了,但是在最近的一次面试中还是答得不尽如人意!再次做一下整理,以便加深理解以及随时查阅. 首先,java.util包中三个重要的接口及特点:List ...
- java基础语法要点<一>(基于1.8)
http://yishouce.com/java/run http://www.shucunwang.com/RunCode/java/ 数据类型 8种基本数据类型及对应的 类型封装器 byte, s ...
- 基础4 Android基础
基础4 Android基础 1. Activity与Fragment的生命周期. Activity生命周期 打开应用 onCreate()->onStart()->onResume 按BA ...
- 基础1 JavaSe基础
JavaSe基础 1. 九种基本数据类型的大小,以及他们的封装类 boolean 无明确指定 Boolean char 16bits Character byte 8bits Byte short 1 ...
- JVM基础(5)-垃圾回收机制
一.对象引用的类型 Java 中的垃圾回收一般是在 Java 堆中进行,因为堆中几乎存放了 Java 中所有的对象实例.谈到 Java 堆中的垃圾回收,自然要谈到引用.在 JDK1.2 之前,Java ...
- java基础解析系列(七)---ThreadLocal原理分析
java基础解析系列(七)---ThreadLocal原理分析 目录 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系列(二)-- ...
随机推荐
- Mybatis文档阅读笔记(明日继续更新...)
今天在编写mybatis的mapper.xml时,发现对sql的配置还不是很熟,有很多一坨一坨的东西,其实是可以抽取成服用的.不过良好的组织代码,还是更重要的.
- [Linux基础]Linux基础知识入门及常见命令.
前言:最近刚安装了Linux系统, 所以学了一些最基本的操作, 在这里把自己总结的笔记记录在这里. 1,V8:192.168.40.10V1:192.168.40.11Linux ip:192.168 ...
- [Java面试四]Strust2总结及在面试中的一些问题.
1. JavaEE软件三层结构和MVC的区别? JavaEE软件三层机构是由sun公司提供JavaEE开发规范的:Web层(表现层).业务逻辑层.数据持久层.[其中WEB层会使用前端控制器模式] MV ...
- Atitit.架构设计趋势 设计模式 ---微服务架构 soa
Atitit.架构设计趋势 设计模式 ---微服务架构 soa 什么是微服务架构?1 .微服务与SOA的关系 :微服务架架构师面向服务架构(SOA)的一种特定实现1 微服务与康威定律2 微服务的一些 ...
- 每天一个linux命令(40):wc命令
Linux系统中的wc(Word Count)命令的功能为统计指定文件中的字节数.字数.行数,并将统计结果显示输出. 1.命令格式: wc [选项]文件... 2.命令功能: 统计指定文件中的字节数. ...
- 每天一个linux命令(29):chgrp命令
在lunix系统里,文件或目录的权限的掌控以拥有者及所诉群组来管理.可以使用chgrp指令取变更文件与目录所属群组,这种方式采用群组名称或群组识别码都可以.Chgrp命令就是change group的 ...
- viewpage listview gridview加载本地大图多图OOM处理办法
很少上博客园写东西了. 最近在写公司项目,由于需要加载本地相册通过viewpager方式来加载, 最后发现直接进入界面就OOM了. 经过几天的整理最终搞定. 现在将加载本地和加载网络图片的缓存工具类贴 ...
- KnockoutJS 3.X API 第三章 计算监控属性(1) 使用计算监控属性
计算监控属性(Computed Observables) 如果你有一个监控属性firstName,和另一个lastName,你要显示的全名?可以使用计算监控属性来实现-它依赖于一个或多个其他监控属性, ...
- easyuidatagrid中load,reload,loadData的区别。
摘要:datagrid中有load,reload,loadData那三个方式,皆是加载数据的,但又有差别.下面让我们一起来看看: 首先,load方法,比如我已经定义一个datagrid的id为grid ...
- android rectF
new Rect(left , top, right , bottom) 这个构造方法需要四个参数这四个参数 指明了什么位置 ?我们就来解释怎么画 这个 矩形 这四个 参数 分别代表的意思是:left ...