Java中一共有4种援用类型(其实还有一些其余的援用类型比方FinalReference):强援用、软援用、弱援用、虚援用。

其中强援用就是咱们常常应用的Object a = new Object(); 这样的模式,在Java中并没有对应的Reference类。

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

问题

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

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

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

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

Reference

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

public abstract class Reference<T> {    //援用的对象    private T referent;            //回收队列,由使用者在Reference的构造函数中指定    volatile ReferenceQueue<? super T> queue;     //当该援用被退出到queue中的时候,该字段被设置为queue中的下一个元素,以造成链表构造    volatile Reference next;    //在GC时,JVM底层会保护一个叫DiscoveredList的链表,寄存的是Reference对象,discovered字段指向的就是链表中的下一个元素,由JVM设置    transient private Reference<T> discovered;      //进行线程同步的锁对象    static private class Lock { }    private static Lock lock = new Lock();    //期待退出queue的Reference对象,在GC时由JVM设置,会有一个java层的线程(ReferenceHandler)源源不断的从pending中提取元素退出到queue    private static Reference<Object> pending = null;}

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

次要分为Native层和Java层两个局部。

Native层在GC时将须要被回收的Reference对象退出到DiscoveredList中(代码在referenceProcessor.cpp中process_discovered_references办法),而后将DiscoveredList的元素挪动到PendingList中(代码在referenceProcessor.cpp中enqueue_discovered_ref_helper办法),PendingList的队首就是Reference类中的pending对象。

看看Java层的代码

private static class ReferenceHandler extends Thread {         ...        public void run() {            while (true) {                tryHandlePending(true);            }        }  } static boolean tryHandlePending(boolean waitForNotify) {        Reference<Object> r;        Cleaner c;        try {            synchronized (lock) {                if (pending != null) {                    r = pending;                     //如果是Cleaner对象,则记录下来,上面做非凡解决                    c = r instanceof Cleaner ? (Cleaner) r : null;                    //指向PendingList的下一个对象                    pending = r.discovered;                    r.discovered = null;                } else {                   //如果pending为null就先期待,当有对象退出到PendingList中时,jvm会执行notify                    if (waitForNotify) {                        lock.wait();                    }                    // retry if waited                    return waitForNotify;                }            }        }         ...        // 如果时CLeaner对象,则调用clean办法进行资源回收        if (c != null) {            c.clean();            return true;        }        //将Reference退出到ReferenceQueue,开发者能够通过从ReferenceQueue中poll元素感知到对象被回收的事件。        ReferenceQueue<? super Object> q = r.queue;        if (q != ReferenceQueue.NULL) q.enqueue(r);        return true; }

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

另外须要留神的是,对于Cleaner类型(继承自虚援用)的对象会有额定的解决:在其指向的对象被回收时,会调用clean办法,该办法次要是用来做对应的资源回收,在堆外内存DirectByteBuffer中就是用Cleaner进行堆外内存的回收,这也是虚援用在java中的典型利用。

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

SoftReference

public class SoftReference<T> extends Reference<T> {    static private long clock;    private long timestamp;    public SoftReference(T referent) {        super(referent);        this.timestamp = clock;    }    public SoftReference(T referent, ReferenceQueue<? super T> q) {        super(referent, q);        this.timestamp = clock;    }    public T get() {        T o = super.get();        if (o != null && this.timestamp != clock)            this.timestamp = clock;        return o;    }}

软援用的实现很简略,就多了两个字段:clock和timestamp。clock是个动态变量,每次GC时都会将该字段设置成以后工夫。timestamp字段则会在每次调用get办法时将其赋值为clock(如果不相等且对象没被回收)。

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

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

size_tReferenceProcessor::process_discovered_reflist(  DiscoveredList               refs_lists[],  ReferencePolicy*             policy,  bool                         clear_referent,  BoolObjectClosure*           is_alive,  OopClosure*                  keep_alive,  VoidClosure*                 complete_gc,  AbstractRefProcTaskExecutor* task_executor){ ...   //还记得上文提到过的DiscoveredList吗?refs_lists就是DiscoveredList。   //对于DiscoveredList的解决分为几个阶段,SoftReference的解决就在第一阶段 ...      for (uint i = 0; i < _max_num_q; i++) {        process_phase1(refs_lists[i], policy,                       is_alive, keep_alive, complete_gc);      } ...}//该阶段的次要目标就是当内存足够时,将对应的SoftReference从refs_list中移除。voidReferenceProcessor::process_phase1(DiscoveredList&    refs_list,                                   ReferencePolicy*   policy,                                   BoolObjectClosure* is_alive,                                   OopClosure*        keep_alive,                                   VoidClosure*       complete_gc) {  DiscoveredListIterator iter(refs_list, keep_alive, is_alive);  // Decide which softly reachable refs should be kept alive.  while (iter.has_next()) {    iter.load_ptrs(DEBUG_ONLY(!discovery_is_atomic() /* allow_null_referent */));    //判断援用的对象是否存活    bool referent_is_dead = (iter.referent() != NULL) && !iter.is_referent_alive();    //如果援用的对象曾经不存活了,则会去调用对应的ReferencePolicy判断该对象是不断要被回收    if (referent_is_dead &&        !policy->should_clear_reference(iter.obj(), _soft_ref_timestamp_clock)) {      if (TraceReferenceGC) {        gclog_or_tty->print_cr("Dropping reference (" INTPTR_FORMAT ": %s"  ") by policy",                               (void *)iter.obj(), iter.obj()->klass()->internal_name());      }      // Remove Reference object from list      iter.remove();      // Make the Reference object active again      iter.make_active();      // keep the referent around      iter.make_referent_alive();      iter.move_to_next();    } else {      iter.next();    }  } ...}

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的should_clear_reference办法则是完全相同:

bool LRUMaxHeapPolicy::should_clear_reference(oop p,                                             jlong timestamp_clock) {  jlong interval = timestamp_clock - java_lang_ref_SoftReference::timestamp(p);  assert(interval >= 0, "Sanity check");  // The interval will be zero if the ref was accessed since the last scavenge/gc.  if(interval <= _max_interval) {    return false;  }  return true;}

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

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

void LRUCurrentHeapPolicy::setup() {  _max_interval = (Universe::get_heap_free_at_last_gc() / M) * SoftRefLRUPolicyMSPerMB;  assert(_max_interval >= 0,"Sanity check");}void LRUMaxHeapPolicy::setup() {  size_t max_heap = MaxHeapSize;  max_heap -= Universe::get_heap_used_at_last_gc();  max_heap /= M;  _max_interval = max_heap * SoftRefLRUPolicyMSPerMB;  assert(_max_interval >= 0,"Sanity check");}

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

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

WeakReference

public class WeakReference<T> extends Reference<T> {    public WeakReference(T referent) {        super(referent);    }    public WeakReference(T referent, ReferenceQueue<? super T> q) {        super(referent, q);    }}

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

size_tReferenceProcessor::process_discovered_reflist(  DiscoveredList               refs_lists[],  ReferencePolicy*             policy,  bool                         clear_referent,  BoolObjectClosure*           is_alive,  OopClosure*                  keep_alive,  VoidClosure*                 complete_gc,  AbstractRefProcTaskExecutor* task_executor){ ...  //Phase 1:将所有不存活然而还不能被回收的软援用从refs_lists中移除(只有refs_lists为软援用的时候,这里policy才不为null)  if (policy != NULL) {    if (mt_processing) {      RefProcPhase1Task phase1(*this, refs_lists, policy, true /*marks_oops_alive*/);      task_executor->execute(phase1);    } else {      for (uint i = 0; i < _max_num_q; i++) {        process_phase1(refs_lists[i], policy,                       is_alive, keep_alive, complete_gc);      }    }  } else { // policy == NULL    assert(refs_lists != _discoveredSoftRefs,           "Policy must be specified for soft references.");  }  // Phase 2:  // 移除所有指向对象还存活的援用  if (mt_processing) {    RefProcPhase2Task phase2(*this, refs_lists, !discovery_is_atomic() /*marks_oops_alive*/);    task_executor->execute(phase2);  } else {    for (uint i = 0; i < _max_num_q; i++) {      process_phase2(refs_lists[i], is_alive, keep_alive, complete_gc);    }  }  // Phase 3:  // 依据clear_referent的值决定是否将不存活对象回收  if (mt_processing) {    RefProcPhase3Task phase3(*this, refs_lists, clear_referent, true /*marks_oops_alive*/);    task_executor->execute(phase3);  } else {    for (uint i = 0; i < _max_num_q; i++) {      process_phase3(refs_lists[i], clear_referent,                     is_alive, keep_alive, complete_gc);    }  }  return total_list_count;}voidReferenceProcessor::process_phase3(DiscoveredList&    refs_list,                                   bool               clear_referent,                                   BoolObjectClosure* is_alive,                                   OopClosure*        keep_alive,                                   VoidClosure*       complete_gc) {  ResourceMark rm;  DiscoveredListIterator iter(refs_list, keep_alive, is_alive);  while (iter.has_next()) {    iter.update_discovered();    iter.load_ptrs(DEBUG_ONLY(false /* allow_null_referent */));    if (clear_referent) {      // NULL out referent pointer      //将Reference的referent字段置为null,之后会被GC回收      iter.clear_referent();    } else {      // keep the referent around      //标记援用的对象为存活,该对象在这次GC将不会被回收      iter.make_referent_alive();    }    ...  }    ...}

不论是弱援用还是其余援用类型,将字段referent置null的操作都产生在process_phase3中,而具体行为是由clear_referent的值决定的。而clear_referent的值则和援用类型相干。

ReferenceProcessorStats ReferenceProcessor::process_discovered_references(  BoolObjectClosure*           is_alive,  OopClosure*                  keep_alive,  VoidClosure*                 complete_gc,  AbstractRefProcTaskExecutor* task_executor,  GCTimer*                     gc_timer) {  NOT_PRODUCT(verify_ok_to_handle_reflists());    ...  //process_discovered_reflist办法的第3个字段就是clear_referent  // Soft references  size_t soft_count = 0;  {    GCTraceTime tt("SoftReference", trace_time, false, gc_timer);    soft_count =      process_discovered_reflist(_discoveredSoftRefs, _current_soft_ref_policy, true,                                 is_alive, keep_alive, complete_gc, task_executor);  }  update_soft_ref_master_clock();  // Weak references  size_t weak_count = 0;  {    GCTraceTime tt("WeakReference", trace_time, false, gc_timer);    weak_count =      process_discovered_reflist(_discoveredWeakRefs, NULL, true,                                 is_alive, keep_alive, complete_gc, task_executor);  }  // Final references  size_t final_count = 0;  {    GCTraceTime tt("FinalReference", trace_time, false, gc_timer);    final_count =      process_discovered_reflist(_discoveredFinalRefs, NULL, false,                                 is_alive, keep_alive, complete_gc, task_executor);  }  // Phantom references  size_t phantom_count = 0;  {    GCTraceTime tt("PhantomReference", trace_time, false, gc_timer);    phantom_count =      process_discovered_reflist(_discoveredPhantomRefs, NULL, false,                                 is_alive, keep_alive, complete_gc, task_executor);  }    ...}

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

但对于Final references和 Phantom references,clear_referent字段传入的是false,也就意味着被这两种援用类型援用的对象,如果没有其余额定解决,只有Reference对象还存活,那援用的对象是不会被回收的。Final references和对象是否重写了finalize办法无关,不在本文剖析范畴之内,咱们接下来看看Phantom references。

PhantomReference

public class PhantomReference<T> extends Reference<T> {    public T get() {        return null;    }    public PhantomReference(T referent, ReferenceQueue<? super T> q) {        super(referent, q);    }}

能够看到虚援用的get办法永远返回null,咱们看个demo。

 public static void demo() throws InterruptedException {        Object obj = new Object();        ReferenceQueue<Object> refQueue =new ReferenceQueue<>();        PhantomReference<Object> phanRef =new PhantomReference<>(obj, refQueue);        Object objg = phanRef.get();        //这里拿到的是null        System.out.println(objg);        //让obj变成垃圾        obj=null;        System.gc();        Thread.sleep(3000);        //gc后会将phanRef退出到refQueue中        Reference<? extends Object> phanRefP = refQueue.remove();         //这里输入true        System.out.println(phanRefP==phanRef);    }

从以上代码中能够看到,虚援用可能在指向对象不可达时失去一个'告诉'(其实所有继承References的类都有这个性能),须要留神的是GC实现后,phanRef.referent仍然指向之前创立Object,也就是说Object对象始终没被回收!

而造成这一景象的起因在上一大节开端曾经说了:对于Final references和 Phantom references,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相干文章,学习材料都会在外面更新,整顿的材料也会放在外面。

感觉写的还不错的就点个赞,加个关注呗!点关注,不迷路,继续更新!!!