java的引用相关知识

关于java的引用类型以及对对象的回收涉及到以下几个类,Reference、Reference子类(SoftReference、WeakReference、PhantomReference)以及ReferenceQueue,这篇文章就重点讲解以下。

一. java对象的引用类型:

  • 1. 强引用(FinalReference)

强引用就是我们经常使用的引用,其写法如下:

StringBuffer buffer = new StringBuffer();

面创建了一个StringBuffer对象,并将这个对象的(强)引用存到变量buffer中。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。

  • 2. 软引用(SoftReference)

如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。如果全部释放完这些对象之后,内存还不足,才会抛出OutOfMemory错误。

由于软引用可到达的对象比弱引用可达到的对象滞留内存时间会长一些,我们可以利用这个特性来做缓存。

SoftReference<Widget> softWidget = new SoftReference<Widget>(widget);

软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中,关于引用队列我们在接下来讲

  • 3. 弱引用(WeakReference)

弱引用简单来说就是将对象留在内存的能力不是那么强的引用。使用WeakReference,垃圾回收器会帮你来决定引用的对象何时回收并且将对象从内存移除。创建弱引用如下:


WeakReference<Widget> weakWidget = new WeakReference<Widget>(widget);

使用weakWidget.get()就可以得到真实的Widget对象,因为弱引用不能阻挡垃圾回收器对其回收,你会发现(当没有任何强引用到widget对象时)使用get时突然返回null。

弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。

  • 其中一个应用就是WeakHashMap,WeakHashMap和HashMap几乎一样,唯一的区别就是它的键(不是值!!!)使用WeakReference引用。当WeakHashMap的键标记为垃圾的时候,这个键对应的条目就会自动被移除。这就避免了上面不需要的Widget对象手动删除的问题。这里是和ReferenceQueue联合使用

  • 另外一个应用就是ThreadLocal中也应用到了WeakReference。ThreadLocal中并没有将ReferenceQueue联合使用

  • 4. 虚引用(PhantomReference)

在Object类里面有个finalize方法,其设计的初衷是在一个对象被真正回收之前,可以用来执行一些清理的工作。但是问题在于垃圾回收器的运行时间是不固定的,所以这些清理工作的实际运行时间也是不能预知的。虚引用(phantom reference)可以解决这个问题。在创建幽灵引用PhantomReference的时候必须要指定一个引用队列。当一个对象的finalize方法已经被调用了之后,这个对象的幽灵引用会被加入到队列中。通过检查该队列里面的内容就知道一个对象是不是已经准备要被回收了。

虚引用指向的对象十分脆弱,我们不可以通过get方法来得到其指向的对象。它的唯一作用就是当其指向的对象被回收之后,自己被加入到引用队列,用作记录该引用指向的对象已被销毁。

虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之 关联的引用队列中。

二. 引用队列(ReferenceQueue)

上面的这些概念中都提到了ReferenceQueue,下面我们就来看看ReferenceQueue是什么.
首先看源码中的介绍

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

查看源代码会发现它很简单,实现了一个队列的入队(enqueue)和出队(poll还有remove)操作,内部元素就是泛型的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的实现。
Reference实例有四种状态(建议直接看源码,翻译的话总会感觉少了点东西)

 *     Active: Subject to special treatment by the garbage collector.  Some
 *     time after the collector detects that the reachability of the
 *     referent has changed to the appropriate state, it changes the
 *     instance's state to either Pending or Inactive, depending upon
 *     whether or not the instance was registered with a queue when it was
 *     created.  In the former case it also adds the instance to the
 *     pending-Reference list.  Newly-created instances are Active.
 *
 *     Pending: An element of the pending-Reference list, waiting to be
 *     enqueued by the Reference-handler thread.  Unregistered instances
 *     are never in this state.
 *
 *     Enqueued: An element of the queue with which the instance was
 *     registered when it was created.  When an instance is removed from
 *     its ReferenceQueue, it is made Inactive.  Unregistered instances are
 *     never in this state.
 *
 *     Inactive: Nothing more to do.  Once an instance becomes Inactive its
 *     state will never change again.

从数据结构上看,Reference类内部主要的成员有

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

     volatile ReferenceQueue<? super T> queue;
     /* When active:   NULL
      *     pending:   this
      *    Enqueued:   next reference in queue (or this if last)
      *    Inactive:   this
      */
     @SuppressWarnings("rawtypes")
     Reference next;

     /* 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 */

      /* 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;

这个queue是通过构造函数传入的,表示创建一个Reference时,要将其注册到那个queue上。

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

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

Queue的另一个作用是可以区分不同状态的Reference。Reference有4种状态,不同状态的reference其queue和next的规则如下不同:

* The state is encoded in the queue and next fields as follows:
*
*     Active: queue = ReferenceQueue with which instance is registered, or
*     ReferenceQueue.NULL if it was not registered with a queue; next =
*     null.
*
*     Pending: queue = ReferenceQueue with which instance is registered;
*     next = this
*
*     Enqueued: queue = ReferenceQueue.ENQUEUED; next = Following instance
*     in queue, or this if at end of list.
*
*     Inactive: queue = ReferenceQueue.NULL; next = this.
*

说说自己的理解:就是Reference有四种状态,每一种状态都都相应的queue和next变量,建议看源码部分的对应关系时要仔细理解。

ReferenceHandler线程

这个线程在Reference类的static构造块中启动,并且被设置为高优先级和daemon状态。此线程要做的事情,是不断的检查pending 是否为null,如果pending不为null,则将pending进行enqueue,否则线程进入wait状态。

ReferenceHandler线程要做的是将pending对象enqueue,但默认我们所提供的queue,也就是从构造函数传入的是null,实际是使用了ReferenceQueue.NULL,Handler线程判断queue为ReferenceQueue.NULL则不进行操作,只有非ReferenceQueue.NULL的queue才会将Reference进行enqueue。

ReferenceQueue.NULL相当于我们提供了一个空的Queue去监听垃圾回收器给我们的反馈,并且对这种反馈不做任何处理。要处理反馈,则必须要提供一个非ReferenceQueue.NULL的queue。

在WeakHashMap则在内部提供了一个非NULL的ReferenceQueue

private final ReferenceQueue<K> queue = new ReferenceQueue<K>();

在 WeakHashMap 添加一个元素时,会使用 此queue来做监听器。见put方法中的下面一句:

tab[i] = new Entry<K,V>(k, value, queue, h, e);

这里Entry是一个内部类,继承了WeakReference

class Entry<K,V> extends WeakReference<K> implements Map.Entry<K,V>

WeakHashMap的 put, size, clear 都会间接或直接的调用到 expungeStaleEntries()方法。

expungeStaleEntries顾名思义,此方法的作用就是将 queue中陈旧的Reference进行删除,因为其内部的referent都已经不可达了。所以也将这个WeakReference包装的key从map中删除。

参考链接

1.并发编程 | ThreadLocal源码深入分析
2.深入分析 ThreadLocal 内存泄漏问题

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,591评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,448评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,823评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,204评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,228评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,190评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,078评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,923评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,334评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,550评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,727评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,428评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,022评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,672评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,826评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,734评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,619评论 2 354