Java引用类型原理剖析,看这篇文章就对了!

点击上方 "程序员小乐"关注, 星标或置顶一起成长

每天凌晨00点00分, 第一时间与你相约

每日英文

No matter how many mistakes you make or how slow you progress, you are still way ahead of everyone who isn't trying.

无论你犯了多少错,或者你进步得有多慢,你都走在了那些不曾尝试的人的前面。

每日掏心

有时候,我们不得不坚强,于是乎,在假装坚强中,就真的越来越坚强。这是真的。


来自:farmerjohngit | 责编:乐乐

链接:github.com/farmerjohngit

程序员小乐(ID:study_tech)第 697 次推文   图片来自网络

往日回顾:安卓系统曝漏洞!有人可能正在用你的手机秘密拍照

   正文   

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

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

问题

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

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

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

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

Reference

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


                                                    
                                                    
  1. publicabstractclassReference<T> {

  2.    //引用的对象

  3.    private T referent;        

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

  5.    volatileReferenceQueue<? super T> queue;

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

  7.    volatileReferencenext;

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

  9.    transientprivateReference<T> discovered;  

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

  11.    staticprivateclassLock{ }

  12.    privatestaticLocklock= newLock();

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

  14.    privatestaticReference<Object> pending = null;

  15. }

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

主要分为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. privatestaticclassReferenceHandlerextendsThread{

  2.         ...

  3.        publicvoid run() {

  4.            while(true) {

  5.                tryHandlePending(true);

  6.            }

  7.        }

  8.  }

  9. staticboolean tryHandlePending(boolean waitForNotify) {

  10.        Reference<Object> r;

  11.        Cleaner c;

  12.        try{

  13.            synchronized(lock) {

  14.                if(pending != null) {

  15.                    r = pending;

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

  17.                    c = r instanceofCleaner? (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.        // 如果时CLeaner对象,则调用clean方法进行资源回收

  33.        if(c != null) {

  34.            c.clean();

  35.            returntrue;

  36.        }

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

  38.        ReferenceQueue<? superObject> q = r.queue;

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

  40.        returntrue;

  41. }

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

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

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

SoftReference

                                                    
                                                    
  1. publicclassSoftReference<T> extendsReference<T> {

  2.    staticprivatelong clock;

  3.    privatelong timestamp;

  4.    publicSoftReference(T referent) {

  5.        super(referent);

  6.        this.timestamp = clock;

  7.    }

  8.    publicSoftReference(T referent, ReferenceQueue<? super T> q) {

  9.        super(referent, q);

  10.        this.timestamp = clock;

  11.    }

  12.    public T get() {

  13.        T o = super.get();

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

  15.            this.timestamp = clock;

  16.        return o;

  17.    }

  18. }

软引用的实现很简单,就多了两个字段: 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. //该阶段的主要目的就是当内存足够时,将对应的SoftReference从refs_list中移除。

  22. void

  23. ReferenceProcessor::process_phase1(DiscoveredList&    refs_list,

  24.                                   ReferencePolicy*   policy,

  25.                                   BoolObjectClosure* is_alive,

  26.                                   OopClosure*        keep_alive,

  27.                                   VoidClosure*       complete_gc) {

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

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

  30.  while(iter.has_next()) {

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

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

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

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

  35.    if(referent_is_dead &&

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

  37.      if(TraceReferenceGC) {

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

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

  40.      }

  41.      // Remove Reference object from list

  42.      iter.remove();

  43.      // Make the Reference object active again

  44.      iter.make_active();

  45.      // keep the referent around

  46.      iter.make_referent_alive();

  47.      iter.move_to_next();

  48.    } else{

  49.      iter.next();

  50.    }

  51.  }

  52. ...

  53. }

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. boolLRUMaxHeapPolicy::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.  // The interval will be zero if the ref was accessed since the last scavenge/gc.

  6.  if(interval <= _max_interval) {

  7.    returnfalse;

  8.  }

  9.  returntrue;

  10. }

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

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


                                                    
                                                    
  1. voidLRUCurrentHeapPolicy::setup() {

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

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

  4. }

  5. voidLRUMaxHeapPolicy::setup() {

  6.  size_t max_heap = MaxHeapSize;

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

  8.  max_heap /= M;

  9.  _max_interval = max_heap * SoftRefLRUPolicyMSPerMB;

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

  11. }

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

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

WeakReference


                                                    
                                                    
  1. publicclassWeakReference<T> extendsReference<T> {

  2.    publicWeakReference(T referent) {

  3.        super(referent);

  4.    }

  5.    publicWeakReference(T referent, ReferenceQueue<? super T> q) {

  6.        super(referent, q);

  7.    }

  8. }

可以看到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.  //Phase 1:将所有不存活但是还不能被回收的软引用从refs_lists中移除(只有refs_lists为软引用的时候,这里policy才不为null)

  13.  if(policy != NULL) {

  14.    if(mt_processing) {

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

  16.      task_executor->execute(phase1);

  17.    } else{

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

  19.        process_phase1(refs_lists[i], policy,

  20.                       is_alive, keep_alive, complete_gc);

  21.      }

  22.    }

  23.  } else{ // policy == NULL

  24.    assert(refs_lists != _discoveredSoftRefs,

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

  26.  }

  27.  // Phase 2:

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

  29.  if(mt_processing) {

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

  31.    task_executor->execute(phase2);

  32.  } else{

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

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

  35.    }

  36.  }

  37.  // Phase 3:

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

  39.  if(mt_processing) {

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

  41.    task_executor->execute(phase3);

  42.  } else{

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

  44.      process_phase3(refs_lists[i], clear_referent,

  45.                     is_alive, keep_alive, complete_gc);

  46.    }

  47.  }

  48.  return total_list_count;

  49. }

  50. void

  51. ReferenceProcessor::process_phase3(DiscoveredList&    refs_list,

  52.                                   bool               clear_referent,

  53.                                   BoolObjectClosure* is_alive,

  54.                                   OopClosure*        keep_alive,

  55.                                   VoidClosure*       complete_gc) {

  56.  ResourceMark rm;

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

  58.  while(iter.has_next()) {

  59.    iter.update_discovered();

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

  61.    if(clear_referent) {

  62.      // NULL out referent pointer

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

  64.      iter.clear_referent();

  65.    } else{

  66.      // keep the referent around

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

  68.      iter.make_referent_alive();

  69.    }

  70.    ...

  71.  }

  72.    ...

  73. }

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


                                                    
                                                    
  1. ReferenceProcessorStatsReferenceProcessor::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.  update_soft_ref_master_clock();

  19.  // Weak references

  20.  size_t weak_count = 0;

  21.  {

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

  23.    weak_count =

  24.      process_discovered_reflist(_discoveredWeakRefs, NULL, true,

  25.                                 is_alive, keep_alive, complete_gc, task_executor);

  26.  }

  27.  // Final references

  28.  size_t final_count = 0;

  29.  {

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

  31.    final_count =

  32.      process_discovered_reflist(_discoveredFinalRefs, NULL, false,

  33.                                 is_alive, keep_alive, complete_gc, task_executor);

  34.  }

  35.  // Phantom references

  36.  size_t phantom_count = 0;

  37.  {

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

  39.    phantom_count =

  40.      process_discovered_reflist(_discoveredPhantomRefs, NULL, false,

  41.                                 is_alive, keep_alive, complete_gc, task_executor);

  42.  }

  43.    ...

  44. }

可以看到,对于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. publicclassPhantomReference<T> extendsReference<T> {

  2.    public T get() {

  3.        returnnull;

  4.    }

  5.    publicPhantomReference(T referent, ReferenceQueue<? super T> q) {

  6.        super(referent, q);

  7.    }

  8. }

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


                                                    
                                                    
  1. publicstaticvoid demo() throwsInterruptedException{

  2.        Object obj = newObject();

  3.        ReferenceQueue<Object> refQueue =newReferenceQueue<>();

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

  5.        Object objg = phanRef.get();

  6.        //这里拿到的是null

  7.        System.out.println(objg);

  8.        //让obj变成垃圾

  9.        obj=null;

  10.        System.gc();

  11.        Thread.sleep(3000);

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

  13.        Reference<? extendsObject> phanRefP = refQueue.remove();

  14.         //这里输出true

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

  16.    }

从以上代码中可以看到,虚引用能够在指向对象不可达时得到一个'通知'(其实所有继承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来实现堆外内存回收的,后续会写篇文章来说说堆外内存的里里外外。

欢迎在留言区留下你的观点,一起讨论提高。如果今天的文章让你有新的启发,学习能力的提升上有新的认识,欢迎转发分享给更多人。

欢迎各位读者加入程序员小乐技术群,在公众号后台回复“加群”或者“学习”即可。

猜你还想看

阿里、腾讯、百度、华为、京东最新面试题汇集

Java8 中用法优雅的 Stream,性能也"优雅"吗?

Spring Boot & Restful API 构建实战

红黑树的理解与Java实现

关注「程序员小乐」,收看更多精彩内容
嘿,你在看吗
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值