Java引用类型原理剖析

Java中一共有4种引用类型(其实还有一些其他的引用类型比如FinalReference):强引用、软引用、弱引用、虚引用。其中强引用就是我们经常使用的 Objecta=newObject(); 这样的形式,在Java中并没有对应的Reference类。

本篇文章主要是分析软引用、弱引用、虚引用的实现,这三种引用类型都是继承于Reference这个类,主要逻辑也在Reference中。

问题

在分析前,先抛几个问题?

1.网上大多数文章对于软引用的介绍是:在内存不足的时候才会被回收,那内存不足是怎么定义的?什么才叫内存不足?

2.网上大多数文章对于虚引用的介绍是:形同虚设,虚引用并不会决定对象的生命周期。主要用来跟踪对象被垃圾回收器回收的活动。真的是这样吗?

3.虚引用在Jdk中有哪些场景下用到了呢?

Reference

我们先看下 Reference.java中的几个字段

 
  1. public abstract class Reference {

  2.    //引用的对象

  3.    private T referent;        

  4.    //回收队列,由使用者在Reference的构造函数中指定

  5.    volatile ReferenceQueue queue;

  6.     //当该引用被加入到queue中的时候,该字段被设置为queue中的下一个元素,以形成链表结构

  7.    volatile Reference next;

  8.    //在GC时,JVM底层会维护一个叫DiscoveredList的链表,存放的是Reference对象,discovered字段指向的就是链表中的下一个元素,由JVM设置

  9.    transient private Reference discovered;  

  10.    //进行线程同步的锁对象

  11.    static private class Lock { }

  12.    private static Lock lock = new Lock();

  13.    //等待加入queue的Reference对象,在GC时由JVM设置,会有一个java层的线程(ReferenceHandler)源源不断的从pending中提取元素加入到queue

  14.    private static Reference pending = null;

  15. }

  16. 一个Reference对象的生命周期如下:

    Java引用类型原理剖析_第1张图片

    主要分为Native层和Java层两个部分。

    Native层在GC时将需要被回收的Reference对象加入到DiscoveredList中(代码在 referenceProcessor.cpp中 process_discovered_references方法),然后将DiscoveredList的元素移动到PendingList中(代码在 referenceProcessor.cpp中 enqueue_discovered_ref_helper方法),PendingList的队首就是Reference类中的pending对象。 具体代码就不分析了,有兴趣的同学可以看看这篇文章。

    http://www.importnew.com/21628.html

    看看Java层的代码

     
    1. private static class ReferenceHandler extends Thread {

    2.         ...

    3.        public void run() {

    4.            while (true) {

    5.                tryHandlePending(true);

    6.            }

    7.        }

    8.  }

    9. static boolean tryHandlePending(boolean waitForNotify) {

    10.        Reference r;

    11.        Cleaner c;

    12.        try {

    13.            synchronized (lock) {

    14.                if (pending != null) {

    15.                    r = pending;

    16.                     //如果是Cleaner对象,则记录下来,下面做特殊处理

    17.                    c = r instanceof Cleaner ? (Cleaner) r : null;

    18.                    //指向PendingList的下一个对象

    19.                    pending = r.discovered;

    20.                    r.discovered = null;

    21.                } else {

    22.                   //如果pending为null就先等待,当有对象加入到PendingList中时,jvm会执行notify

    23.                    if (waitForNotify) {

    24.                        lock.wait();

    25.                    }

    26.                    // retry if waited

    27.                    return waitForNotify;

    28.                }

    29.            }

    30.        }

    31.        ...

    32.  

    33.        // 如果时CLeaner对象,则调用clean方法进行资源回收

    34.        if (c != null) {

    35.            c.clean();

    36.            return true;

    37.        }

    38.        //将Reference加入到ReferenceQueue,开发者可以通过从ReferenceQueue中poll元素感知到对象被回收的事件。

    39.        ReferenceQueue q = r.queue;

    40.        if (q != ReferenceQueue.NULL) q.enqueue(r);

    41.        return true;

    42. }

    43. 流程比较简单:就是源源不断的从PendingList中提取出元素,然后将其加入到ReferenceQueue中去,开发者可以通过从ReferenceQueue中poll元素感知到对象被回收的事件。

      另外需要注意的是,对于Cleaner类型(继承自虚引用)的对象会有额外的处理:在其指向的对象被回收时,会调用clean方法,该方法主要是用来做对应的资源回收,在堆外内存DirectByteBuffer中就是用Cleaner进行堆外内存的回收,这也是虚引用在java中的典型应用。

      看完了Reference的实现,再看看几个实现类里,各自有什么不同。

      SoftReference

       
      1. public class SoftReference extends Reference {

      2.  

      3.    static private long clock;

      4.  

      5.    private long timestamp;

      6.  

      7.    public SoftReference(T referent) {

      8.        super(referent);

      9.        this.timestamp = clock;

      10.    }

      11.  

      12.    public SoftReference(T referent, ReferenceQueue q) {

      13.        super(referent, q);

      14.        this.timestamp = clock;

      15.    }

      16.  

      17.    public T get() {

      18.        T o = super.get();

      19.        if (o != null && this.timestamp != clock)

      20.            this.timestamp = clock;

      21.        return o;

      22.    }

      23.  

      24. }

      软引用的实现很简单,就多了两个字段: clock和 timestamp。 clock是个静态变量,每次GC时都会将该字段设置成当前时间。 timestamp字段则会在每次调用get方法时将其赋值为 clock(如果不相等且对象没被回收)。

      那这两个字段的作用是什么呢?这和软引用在内存不够的时候才被回收,又有什么关系呢?

      这些还得看JVM的源码才行,因为决定对象是否需要被回收都是在GC中实现的。

       
      1. size_t

      2. ReferenceProcessor::process_discovered_reflist(

      3.  DiscoveredList               refs_lists[],

      4.  ReferencePolicy*             policy,

      5.  bool                         clear_referent,

      6.  BoolObjectClosure*           is_alive,

      7.  OopClosure*                  keep_alive,

      8.  VoidClosure*                 complete_gc,

      9.  AbstractRefProcTaskExecutor* task_executor)

      10. {

      11. ...

      12.   //还记得上文提到过的DiscoveredList吗?refs_lists就是DiscoveredList。

      13.   //对于DiscoveredList的处理分为几个阶段,SoftReference的处理就在第一阶段

      14. ...

      15.      for (uint i = 0; i < _max_num_q; i++) {

      16.        process_phase1(refs_lists[i], policy,

      17.                       is_alive, keep_alive, complete_gc);

      18.      }

      19. ...

      20. }

      21.  

      22. //该阶段的主要目的就是当内存足够时,将对应的SoftReference从refs_list中移除。

      23. void

      24. ReferenceProcessor::process_phase1(DiscoveredList&    refs_list,

      25.                                   ReferencePolicy*   policy,

      26.                                   BoolObjectClosure* is_alive,

      27.                                   OopClosure*        keep_alive,

      28.                                   VoidClosure*       complete_gc) {

      29.  

      30.  DiscoveredListIterator iter(refs_list, keep_alive, is_alive);

      31.  // Decide which softly reachable refs should be kept alive.

      32.  while (iter.has_next()) {

      33.    iter.load_ptrs(DEBUG_ONLY(!discovery_is_atomic() /* allow_null_referent */));

      34.    //判断引用的对象是否存活

      35.    bool referent_is_dead = (iter.referent() != NULL) && !iter.is_referent_alive();

      36.    //如果引用的对象已经不存活了,则会去调用对应的ReferencePolicy判断该对象是不时要被回收

      37.    if (referent_is_dead &&

      38.        !policy->should_clear_reference(iter.obj(), _soft_ref_timestamp_clock)) {

      39.      if (TraceReferenceGC) {

      40.        gclog_or_tty->print_cr("Dropping reference (" INTPTR_FORMAT ": %s"  ") by policy",

      41.                               (void *)iter.obj(), iter.obj()->klass()->internal_name());

      42.      }

      43.      // Remove Reference object from list

      44.      iter.remove();

      45.      // Make the Reference object active again

      46.      iter.make_active();

      47.      // keep the referent around

      48.      iter.make_referent_alive();

      49.      iter.move_to_next();

      50.    } else {

      51.      iter.next();

      52.    }

      53.  }

      54. ...

      55. }

      refs_lists中存放了本次GC发现的某种引用类型(虚引用、软引用、弱引用等),而 process_discovered_reflist方法的作用就是将不需要被回收的对象从 refs_lists移除掉, refs_lists最后剩下的元素全是需要被回收的元素,最后会将其第一个元素赋值给上文提到过的 Reference.java#pending字段。

      ReferencePolicy一共有4种实现:NeverClearPolicy,AlwaysClearPolicy,LRUCurrentHeapPolicy,LRUMaxHeapPolicy。其中NeverClearPolicy永远返回false,代表永远不回收SoftReference,在JVM中该类没有被使用,AlwaysClearPolicy则永远返回true,在 referenceProcessor.hpp#setup方法中中可以设置policy为AlwaysClearPolicy,至于什么时候会用到AlwaysClearPolicy,大家有兴趣可以自行研究。

      LRUCurrentHeapPolicy和LRUMaxHeapPolicy的shouldclearreference方法则是完全相同:

       
      1. bool LRUMaxHeapPolicy::should_clear_reference(oop p,

      2.                                             jlong timestamp_clock) {

      3.  jlong interval = timestamp_clock - java_lang_ref_SoftReference::timestamp(p);

      4.  assert(interval >= 0, "Sanity check");

      5.  

      6.  // The interval will be zero if the ref was accessed since the last scavenge/gc.

      7.  if(interval <= _max_interval) {

      8.    return false;

      9.  }

      10.  

      11.  return true;

      12. }

      timestamp_clock就是SoftReference的静态字段 clock, java_lang_ref_SoftReference::timestamp(p)对应是字段 timestamp。如果上次GC后有调用 SoftReference#get, interval值为0,否则为若干次GC之间的时间差。

      _max_interval则代表了一个临界值,它的值在LRUCurrentHeapPolicy和LRUMaxHeapPolicy两种策略中有差异。

       
      1. void LRUCurrentHeapPolicy::setup() {

      2.  _max_interval = (Universe::get_heap_free_at_last_gc() / M) * SoftRefLRUPolicyMSPerMB;

      3.  assert(_max_interval >= 0,"Sanity check");

      4. }

      5.  

      6. void LRUMaxHeapPolicy::setup() {

      7.  size_t max_heap = MaxHeapSize;

      8.  max_heap -= Universe::get_heap_used_at_last_gc();

      9.  max_heap /= M;

      10.  

      11.  _max_interval = max_heap * SoftRefLRUPolicyMSPerMB;

      12.  assert(_max_interval >= 0,"Sanity check");

      13. }

      其中 SoftRefLRUPolicyMSPerMB默认为1000,前者的计算方法和上次GC后可用堆大小有关,后者计算方法和(堆大小-上次gc时堆使用大小)有关。

      看到这里你就知道SoftReference到底什么时候被被回收了,它和使用的策略(默认应该是LRUCurrentHeapPolicy),堆可用大小,该SoftReference上一次调用get方法的时间都有关系。

      WeakReference

       
      1. public class WeakReference extends Reference {

      2.  

      3.    public WeakReference(T referent) {

      4.        super(referent);

      5.    }

      6.  

      7.    public WeakReference(T referent, ReferenceQueue q) {

      8.        super(referent, q);

      9.    }

      10.  

      11. }

      可以看到WeakReference在Java层只是继承了Reference,没有做任何的改动。那referent字段是什么时候被置为null的呢?要搞清楚这个问题我们再看下上文提到过的 process_discovered_reflist方法:

       
      1. size_t

      2. ReferenceProcessor::process_discovered_reflist(

      3.  DiscoveredList               refs_lists[],

      4.  ReferencePolicy*             policy,

      5.  bool                         clear_referent,

      6.  BoolObjectClosure*           is_alive,

      7.  OopClosure*                  keep_alive,

      8.  VoidClosure*                 complete_gc,

      9.  AbstractRefProcTaskExecutor* task_executor)

      10. {

      11. ...

      12.  

      13.  //Phase 1:将所有不存活但是还不能被回收的软引用从refs_lists中移除(只有refs_lists为软引用的时候,这里policy才不为null)

      14.  if (policy != NULL) {

      15.    if (mt_processing) {

      16.      RefProcPhase1Task phase1(*this, refs_lists, policy, true /*marks_oops_alive*/);

      17.      task_executor->execute(phase1);

      18.    } else {

      19.      for (uint i = 0; i < _max_num_q; i++) {

      20.        process_phase1(refs_lists[i], policy,

      21.                       is_alive, keep_alive, complete_gc);

      22.      }

      23.    }

      24.  } else { // policy == NULL

      25.    assert(refs_lists != _discoveredSoftRefs,

      26.           "Policy must be specified for soft references.");

      27.  }

      28.  

      29.  // Phase 2:

      30.  // 移除所有指向对象还存活的引用

      31.  if (mt_processing) {

      32.    RefProcPhase2Task phase2(*this, refs_lists, !discovery_is_atomic() /*marks_oops_alive*/);

      33.    task_executor->execute(phase2);

      34.  } else {

      35.    for (uint i = 0; i < _max_num_q; i++) {

      36.      process_phase2(refs_lists[i], is_alive, keep_alive, complete_gc);

      37.    }

      38.  }

      39.  

      40.  // Phase 3:

      41.  // 根据clear_referent的值决定是否将不存活对象回收

      42.  if (mt_processing) {

      43.    RefProcPhase3Task phase3(*this, refs_lists, clear_referent, true /*marks_oops_alive*/);

      44.    task_executor->execute(phase3);

      45.  } else {

      46.    for (uint i = 0; i < _max_num_q; i++) {

      47.      process_phase3(refs_lists[i], clear_referent,

      48.                     is_alive, keep_alive, complete_gc);

      49.    }

      50.  }

      51.  

      52.  return total_list_count;

      53. }

      54.  

      55. void

      56. ReferenceProcessor::process_phase3(DiscoveredList&    refs_list,

      57.                                   bool               clear_referent,

      58.                                   BoolObjectClosure* is_alive,

      59.                                   OopClosure*        keep_alive,

      60.                                   VoidClosure*       complete_gc) {

      61.  ResourceMark rm;

      62.  DiscoveredListIterator iter(refs_list, keep_alive, is_alive);

      63.  while (iter.has_next()) {

      64.    iter.update_discovered();

      65.    iter.load_ptrs(DEBUG_ONLY(false /* allow_null_referent */));

      66.    if (clear_referent) {

      67.      // NULL out referent pointer

      68.      //将Reference的referent字段置为null,之后会被GC回收

      69.      iter.clear_referent();

      70.    } else {

      71.      // keep the referent around

      72.      //标记引用的对象为存活,该对象在这次GC将不会被回收

      73.      iter.make_referent_alive();

      74.    }

      75.    ...

      76.  }

      77.    ...

      78. }

      不管是弱引用还是其他引用类型,将字段referent置null的操作都发生在 process_phase3中,而具体行为是由 clear_referent的值决定的。而 clear_referent的值则和引用类型相关。

       
      1. ReferenceProcessorStats ReferenceProcessor::process_discovered_references(

      2.  BoolObjectClosure*           is_alive,

      3.  OopClosure*                  keep_alive,

      4.  VoidClosure*                 complete_gc,

      5.  AbstractRefProcTaskExecutor* task_executor,

      6.  GCTimer*                     gc_timer) {

      7.  NOT_PRODUCT(verify_ok_to_handle_reflists());

      8.    ...

      9.  //process_discovered_reflist方法的第3个字段就是clear_referent

      10.  // Soft references

      11.  size_t soft_count = 0;

      12.  {

      13.    GCTraceTime tt("SoftReference", trace_time, false, gc_timer);

      14.    soft_count =

      15.      process_discovered_reflist(_discoveredSoftRefs, _current_soft_ref_policy, true,

      16.                                 is_alive, keep_alive, complete_gc, task_executor);

      17.  }

      18.  

      19.  update_soft_ref_master_clock();

      20.  

      21.  // Weak references

      22.  size_t weak_count = 0;

      23.  {

      24.    GCTraceTime tt("WeakReference", trace_time, false, gc_timer);

      25.    weak_count =

      26.      process_discovered_reflist(_discoveredWeakRefs, NULL, true,

      27.                                 is_alive, keep_alive, complete_gc, task_executor);

      28.  }

      29.  

      30.  // Final references

      31.  size_t final_count = 0;

      32.  {

      33.    GCTraceTime tt("FinalReference", trace_time, false, gc_timer);

      34.    final_count =

      35.      process_discovered_reflist(_discoveredFinalRefs, NULL, false,

      36.                                 is_alive, keep_alive, complete_gc, task_executor);

      37.  }

      38.  

      39.  // Phantom references

      40.  size_t phantom_count = 0;

      41.  {

      42.    GCTraceTime tt("PhantomReference", trace_time, false, gc_timer);

      43.    phantom_count =

      44.      process_discovered_reflist(_discoveredPhantomRefs, NULL, false,

      45.                                 is_alive, keep_alive, complete_gc, task_executor);

      46.  }

      47.    ...

      48. }

      可以看到,对于Soft references和Weak references clear_referent字段传入的都是true,这也符合我们的预期:对象不可达后,引用字段就会被置为null,然后对象就会被回收(对于软引用来说,如果内存足够的话,在Phase 1,相关的引用就会从refslist中被移除,到Phase 3时refslist为空集合)。

      但对于Final references和 Phantom references, clear_referent字段传入的是false,也就意味着被这两种引用类型引用的对象,如果没有其他额外处理,只要Reference对象还存活,那引用的对象是不会被回收的。Final references和对象是否重写了finalize方法有关,不在本文分析范围之内,我们接下来看看Phantom references。

      PhantomReference

       
      1. public class PhantomReference extends Reference {

      2.  

      3.    public T get() {

      4.        return null;

      5.    }

      6.  

      7.    public PhantomReference(T referent, ReferenceQueue q) {

      8.        super(referent, q);

      9.    }

      10.  

      11. }

      可以看到虚引用的get方法永远返回null,我们看个demo。

       
      1. public static void demo() throws InterruptedException {

      2.        Object obj = new Object();

      3.        ReferenceQueue refQueue =new ReferenceQueue<>();

      4.        PhantomReference phanRef =new PhantomReference<>(obj, refQueue);

      5.  

      6.        Object objg = phanRef.get();

      7.        //这里拿到的是null

      8.        System.out.println(objg);

      9.        //让obj变成垃圾

      10.        obj=null;

      11.        System.gc();

      12.        Thread.sleep(3000);

      13.        //gc后会将phanRef加入到refQueue中

      14.        Reference phanRefP = refQueue.remove();

      15.         //这里输出true

      16.        System.out.println(phanRefP==phanRef);

      17.    }

      18. 从以上代码中可以看到,虚引用能够在指向对象不可达时得到一个'通知'(其实所有继承References的类都有这个功能),需要注意的是GC完成后,phanRef.referent依然指向之前创建Object,也就是说Object对象一直没被回收!

        而造成这一现象的原因在上一小节末尾已经说了: 对于Finalreferences和Phantomreferences,clear_referent 字段传入的时false,也就意味着被这两种引用类型引用的对象,如果没有其他额外处理,在GC中是不会被回收的。

        对于虚引用来说,从 refQueue.remove();得到引用对象后,可以调用 clear方法强行解除引用和对象之间的关系,使得对象下次可以GC时可以被回收掉。

        End

        针对文章开头提出的几个问题,看完分析,我们已经能给出回答:

        1.我们经常在网上看到软引用的介绍是:在内存不足的时候才会回收,那内存不足是怎么定义的?为什么才叫内存不足?

        软引用会在内存不足时被回收,内存不足的定义和该引用对象get的时间以及当前堆可用内存大小都有关系,计算公式在上文中也已经给出。

        2.网上对于虚引用的介绍是:形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。主要用来跟踪对象被垃圾回收器回收的活动。真的是这样吗?

        严格的说,虚引用是会影响对象生命周期的,如果不做任何处理,只要虚引用不被回收,那其引用的对象永远不会被回收。所以一般来说,从ReferenceQueue中获得PhantomReference对象后,如果PhantomReference对象不会被回收的话(比如被其他GC ROOT可达的对象引用),需要调用 clear方法解除PhantomReference和其引用对象的引用关系。

        3.虚引用在Jdk中有哪些场景下用到了呢?

        DirectByteBuffer中是用虚引用的子类 Cleaner.java来实现堆外内存回收的

        你可能感兴趣的:(Java基础)