Java Reference源码解析

Reference对象封装了其它对象的引用,可以和普通的对象一样操作,在一定的限制条件下,支持和垃圾收集器的交互。即可以使用Reference对象来引用其它对象,但是最后还是会被垃圾收集器回收。程序有时候也需要在对象回收后被通知,以告知对象的可达性发生变更。

Java提供了四种不同类型的引用,引用级别从高到低分别为FinalReference,SoftReference,WeakReference,PhantomReference。其中FinalReference不对外提供使用。每种类型对应着不同级别的可达性。

简介

强引用FinalReference

强引用指的是,程序中有直接可达的引用,而不需要通过任何引用对象,如Object obj = new Object();中,obj为强引用。

软引用SoftReference

软引用,非强引用,但是可以通过软引用对象来访问。软引用的对象,只有在内存不足的时候(抛出OOM异常前),垃圾收集器会决定回收该软引用所指向的对象。软引用通常用于实现内存敏感的缓存。

SoftReference<Object> softRef = new SoftReference<Object>(new Object());

弱引用WeakReference

弱引用,非强引用和软引用,但是可以通过弱引用对象来访问。弱引用的对象,不管内存是否足够,只要被垃圾收集器发现,该引用的对象就会被回收。实际的应用见WeakHashMap等。

WeakReference<Object> weakRef = new WeakReference<Object>(new Object());

虚引用PhantomReference

虚引用,该引用必须和引用队列(ReferenceQueue)一起使用,一般用于实现追踪垃圾收集器的回收动作,比如在对象被回收的时候,会调用该对象的finalize方法,在使用虚引用可以实现该动作,也更加安全。

Object obj = new Object();
ReferenceQueue<Object> refQueue = new ReferenceQueue<>();
PhantomReference<Object> phantom = new PhantomReference<Object>(obj, refQueue);
ReferenceQueue

该队列作为引用中的一员,可以和上述三种引用类型组合使用,该队列的作用是:创建Reference时,将Queue注册到Reference中,当该Reference所引用的对象被垃圾收集器回收时,会将该Reference放到该队列中,相当于一种通知机制。
示例 Demo1:

ReferenceQueue queue = new ReferenceQueue();

WeakReference reference = new WeakReference(new Object(), queue);
System.out.println(reference);
System.gc();

Reference reference1 = queue.remove();
System.out.println(reference1);

源码分析

Reference和ReferenceQueue

Reference内部有几个比较重要的属性

// 用于保存对象的引用,GC会根据不同Reference来特别对待
private T referent;
// 如果需要通知机制,则保存的对对应的队列
ReferenceQueue<? super T> queue;
/* 这个用于实现一个单向循环链表,用以将保存需要由ReferenceHandler处理的引用 */
Reference next;

static private class Lock { };
// 锁,用于同步pending队列的进队和出队
private static Lock lock = new Lock();
// 此属性保存一个PENDING的队列,配合上述next一起使用
private static Reference pending = null;

状态图

内部类ReferenceHandler

ReferenceHandler作为Reference的静态内部类,用于实现将pending队列里面的Reference实例依次添加到不同的ReferenceQueue中(取决于Reference里面的queue)。该pending的元素由GC负责加入。
注:这里对pending队列进行加锁,个人认为是因为GC线程可能和ReferenceHandler所在的线程并发执行,如GC采用CMS并发收集的时候。

如下代码所示

// 此线程在静态块中启动,即一旦使用了Reference,则会启动该线程
private static class ReferenceHandler extends Thread {
  public void run() {
    for (;;) {

      Reference r;
      synchronized (lock) {
        if (pending != null) {
          r = pending;
          Reference rn = r.next;
          // 从pending中取下一个元素,如果后继为空,则next指向自身   pending = (rn == r) ? null : rn;
          r.next = r;
        } else {
          try {
            // 没有则等待,后续加入元素会调用lock.notify唤醒
            lock.wait();
          } catch (InterruptedException x) { }
          continue;
        }
      }
      // ...
      ReferenceQueue q = r.queue;
      // 如果该Reference注册了对应的Queue,则加入到该Queue中
      if (q != ReferenceQueue.NULL) q.enqueue(r);
    }
  }
}

ReferenceQueue属性

// 用于标识没有注册Queue
static ReferenceQueue NULL = new Null();
// 用于标识已经处于对应的Queue中
static ReferenceQueue ENQUEUED = new Null();

static private class Lock { };
/* 互斥锁,用于同步ReferenceHandler的enqueue和用户线程操作的remove和poll出队操作 */
private Lock lock = new Lock();
// 队列
private volatile Reference<? extends T> head = null;
// 队列中的元素个数
private long queueLength = 0;

ReferenceQueue.enqueue

只会通过Reference里要调用该方法,用于将Reference放入到当前队列中

boolean enqueue(Reference<? extends T> r) {
  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);
      }
      // 通知当前挂起的线程(调用remove时有可能会挂起)
      lock.notifyAll();
      return true;
    }
  }
}

ReferenceQueue.remove

public Reference<? extends T> remove(long timeout)
  throws IllegalArgumentException, InterruptedException
{
  if (timeout < 0) {
    throw new IllegalArgumentException("Negative timeout value");
  }
  synchronized (lock) {
    // 从队列中取出一个元素
    Reference<? extends T> r = reallyPoll();
    // 如果不为空,则直接返回
    if (r != null) return r;
    for (;;) {
      // 否则等待,由enqueue时notify唤醒
      lock.wait(timeout);
      r = reallyPoll();
      if (r != null) return r;
      if (timeout != 0) return null;
    }
  }
}

具体执行流程

以上述示例Demo1作为分析

// 创建一个引用队列
ReferenceQueue queue = new ReferenceQueue();

// 创建虚引用,此时状态为Active,并且Reference.pending为空,当前Reference.queue = 上面创建的queue,并且next=null
WeakReference reference = new WeakReference(new Object(), queue);
System.out.println(reference);
// 当GC执行后,由于是虚引用,所以回收该object对象,并且置于pending上,此时reference的状态为PENDING
System.gc();

/* ReferenceHandler从pending中取下该元素,并且将该元素放入到queue中,此时Reference状态为ENQUEUED,Reference.queue = ReferenceENQUEUED */

/* 当从queue里面取出该元素,则变为INACTIVE,Reference.queue = Reference.NULL */
Reference reference1 = queue.remove();
System.out.println(reference1);

应用 - WeakHashMap

WeakHashMap在使用上和HashMap类型,都是Hash + 链表解决冲突,唯一不同点在于前者的Key是使用虚引用来实现的,即当进行垃圾回收的时候,就是被回收掉,此时WeakHashMap会在下次操作的时候,根据被回收掉的Key,从Map里面移除掉。

Entry

当创建Entry的时候,会注册进当前Map属性的queue,当key被回收后,则该Entry会被放入到queue中,每当操作Map的时候,才会将原有的Value清除掉。(由expungeStaleEntries方法来进行,并且没有启动一个单独的线程来处理,没有必要,这样子简化了逻辑以及避免锁的开销)

// 外部WeakHashMap属性
private final ReferenceQueue<Object> queue = new ReferenceQueue<>();

/* 这里采用了集成WeakReference而不是直接使用,是因为当被回收的时候,具体的Key是不知道的,这里需要往WeakReference额外加入一些属性,以便在被回收后通知时,能够定位到具体的Key/value */
private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
  // 这里属性不能加入key,否则会导致存在强引用而不能被视为WeakReference回收掉
  V value;
  int hash;
  Entry<K,V> next;

  Entry(Object key, V value,
     ReferenceQueue<Object> queue,
     int hash, Entry<K,V> next) {
    super(key, queue);
    this.value = value;
    this.hash = hash;
    this.next = next;
  }
  // ...
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Java中几个Reference常见的作用详解

    前言 Java中几个Reference作用,也是面试的时候经常问到的问题,以前总是记一次忘一次,现在有时间,索性写个demo测试一把.下面来一起看看详细的介绍: 具体代码如下: JVM 参数: -Xmx10m -Xms5m -XX:+PrintGC SoftReference的时候: weakReference的时候: StrongReference: 由于strong是JVM默认的,这里就不做了,直接就是一点都不会被回收,直至OOM PhantomReference: 虚引用并不会改变内存回收

  • Java 中 Reference用法详解

    Java  Reference详解 在 jdk 1.2 及其以后,引入了强引用.软引用.弱引用.虚引用这四个概念.网上很多关于这四个概念的解释,但大多是概念性的泛泛而谈,今天我结合着代码分析了一下,首先我们先来看定义与大概解释(引用类型在包 Java.lang.ref 里). 1.强引用(StrongReference) 强引用不会被GC回收,并且在java.lang.ref里也没有实际的对应类型.举个例子来说: Object obj = new Object(); 这里的obj引用便是一个强引

  • Java Reference源码解析

    Reference对象封装了其它对象的引用,可以和普通的对象一样操作,在一定的限制条件下,支持和垃圾收集器的交互.即可以使用Reference对象来引用其它对象,但是最后还是会被垃圾收集器回收.程序有时候也需要在对象回收后被通知,以告知对象的可达性发生变更. Java提供了四种不同类型的引用,引用级别从高到低分别为FinalReference,SoftReference,WeakReference,PhantomReference.其中FinalReference不对外提供使用.每种类型对应着不

  • java TreeMap源码解析详解

    java TreeMap源码解析详解 在介绍TreeMap之前,我们来了解一种数据结构:排序二叉树.相信学过数据结构的同学知道,这种结构的数据存储形式在查找的时候效率非常高. 如图所示,这种数据结构是以二叉树为基础的,所有的左孩子的value值都是小于根结点的value值的,所有右孩子的value值都是大于根结点的.这样做的好处在于:如果需要按照键值查找数据元素,只要比较当前结点的value值即可(小于当前结点value值的,往左走,否则往右走),这种方式,每次可以减少一半的操作,所以效率比较高

  • JAVA Vector源码解析和示例代码

    第1部分 Vector介绍Vector 是矢量队列,它是JDK1.0版本添加的类.继承于AbstractList,实现了List, RandomAccess, Cloneable这些接口.Vector 继承了AbstractList,实现了List:所以,它是一个队列,支持相关的添加.删除.修改.遍历等功能.Vector 实现了RandmoAccess接口,即提供了随机访问功能.RandmoAccess是java中用来被List实现,为List提供快速访问功能的.在Vector中,我们即可以通过

  • Java多线程之ReentrantReadWriteLock源码解析

    一.介绍 1.1 ReentrantReadWriteLock ReentrantReadWriteLock 是一个读写锁,允许多个读或者一个写线程在执行. 内部的 Sync 继承自 AQS,这个 Sync 包含一个共享读锁 ReadLock 和一个独占写锁 WriteLock. 该锁可以设置公平和非公平,默认非公平. 一个持有写锁的线程可以获取读锁.如果该线程先持有写锁,再持有读锁并释放写锁,称为锁降级. WriteLock支持Condition并且与ReentrantLock语义一致,而Re

  • Java1.7全网最深入HashMap源码解析

    目录 存储结构 属性成员 构造函数: hash方法 Map中添加数据 put方法 流程图 源码 inflateTable方法 putForNullKey方法 addEntry方法 createEntry方法 扩容方法 resize方法 transfer方法 从HashMap中获取数据 get方法 从HashMap中删除数据 remove方法 对HashMap的其他操作 1.7和1.8版本区别 数据结构 hash值计算方式 扩容机制 存储结构 内部包含了一个 Entry 类型的数组 table.E

  • java.lang.Void类源码解析

    在一次源码查看ThreadGroup的时候,看到一段代码,为以下: /* * @throws NullPointerException if the parent argument is {@code null} * @throws SecurityException if the current thread cannot create a * thread in the specified thread group. */ private static Void checkParentAcc

  • Java源码解析之TypeVariable详解

    TypeVariable,类型变量,描述类型,表示泛指任意或相关一类类型,也可以说狭义上的泛型(泛指某一类类型),一般用大写字母作为变量,比如K.V.E等. 源码 public interface TypeVariable<D extends GenericDeclaration> extends Type { //获得泛型的上限,若未明确声明上边界则默认为Object Type[] getBounds(); //获取声明该类型变量实体(即获得类.方法或构造器名) D getGenericDe

  • Java源码解析之GenericDeclaration详解

    学习别人实现某个功能的设计思路,来提高自己的编程水平.话不多说,下面进入正题. GenericDeclaration 可以声明类型变量的实体的公共接口,也就是说,只有实现了该接口才能在对应的实体上声明(定义)类型变量,这些实体目前只有三个:Class(类).Construstor(构造器).Method(方法)(详见:Java源码解析之TypeVariable详解 源码 public interface GenericDeclaration { //获得声明列表上的类型变量数组 public T

  • Java源码解析之object类

    在源码的阅读过程中,可以了解别人实现某个功能的涉及思路,看看他们是怎么想,怎么做的.接下来,我们看看这篇Java源码解析之object的详细内容. Java基类Object java.lang.Object,Java所有类的父类,在你编写一个类的时候,若无指定父类(没有显式extends一个父类)编译器(一般编译器完成该步骤)会默认的添加Object为该类的父类(可以将该类反编译看其字节码,不过貌似Java7自带的反编译javap现在看不到了). 再说的详细点:假如类A,没有显式继承其他类,编译

  • java中break和continue源码解析

    在自己学习java语言的过程中,很容易把break和continue的用法混淆.为了便于以后快速查阅及温习,在此特留学习笔记一份. 简述 在任何迭代语句的主体部分,都可以用break和continue控制循环的流程.其中,break用于强行退出循环,不执行循环中剩余的语句.而continue则停止执行当前迭代,然后退回循环起始处,开始下一次迭代. 源码 下面这个程序向大家展示了break和continue在for和while循环中的例子: package com.mufeng.thefourth

随机推荐