引用类型(Java四种引用类型原理你真的搞明白了吗?)
引用类型(Java四种引用类型原理你真的搞明白了吗?)
Java中一共有4种引用类型(其实还有一些其他的引用类型比如FinalReference):强引用、软引用、弱引用、虚引用。
其中强引用就是我们经常使用的Object a = new Object(); 这样的形式,在Java中并没有对应的Reference类。
本篇文章主要是分析软引用、弱引用、虚引用的实现,这三种引用类型都是继承于Reference这个类,主要逻辑也在Reference中。问题
在分析前,先抛几个问题?
1.网上大多数文章对于软引用的介绍是:在内存不足的时候才会被回收,那内存不足是怎么定义的?什么才叫内存不足?
2.网上大多数文章对于虚引用的介绍是:形同虚设,虚引用并不会决定对象的生命周期。主要用来跟踪对象被垃圾回收器回收的活动。真的是这样吗?
3.虚引用在Jdk中有哪些场景下用到了呢?Reference
我们先看下Reference.java中的几个字段publicabstractclassReference<T>{//引用的对象 privateTreferent; //回收队列,由使用者在Reference的构造函数中指定 volatileReferenceQueue<?superT>queue;//当该引用被加入到queue中的时候,该字段被设置为queue中的下一个元素,以形成链表结构 volatileReferencenext;//在GC时,JVM底层会维护一个叫DiscoveredList的链表,存放的是Reference对象,discovered字段指向的就是链表中的下一个元素,由JVM设置 transientprivateReference<T>discovered; //进行线程同步的锁对象 staticprivateclassLock{}privatestaticLocklock=newLock();//等待加入queue的Reference对象,在GC时由JVM设置,会有一个java层的线程(ReferenceHandler)源源不断的从pending中提取元素加入到queue privatestaticReference<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层的代码privatestaticclassReferenceHandlerextendsThread{ ...publicvoidrun(){while(true){ tryHandlePending(true); } } } staticbooleantryHandlePending(booleanwaitForNotify){ Reference<Object>r; Cleanerc;try{synchronized(lock){if(pending!=null){ r=pending;//如果是Cleaner对象,则记录下来,下面做特殊处理 c=rinstanceofCleaner?(Cleaner)r:null;//指向PendingList的下一个对象 pending=r.discovered; r.discovered=null; }else{//如果pending为null就先等待,当有对象加入到PendingList中时,jvm会执行notify if(waitForNotify){ lock.wait(); }//retryifwaited returnwaitForNotify; } } } ...//如果时CLeaner对象,则调用clean方法进行资源回收 if(c!=null){ c.clean();returntrue; }//将Reference加入到ReferenceQueue,开发者可以通过从ReferenceQueue中poll元素感知到对象被回收的事件。 ReferenceQueue<?superObject>q=r.queue;if(q!=ReferenceQueue.NULL)q.enqueue(r);returntrue; }
流程比较简单:就是源源不断的从PendingList中提取出元素,然后将其加入到ReferenceQueue中去,开发者可以通过从ReferenceQueue中poll元素感知到对象被回收的事件。
另外需要注意的是,对于Cleaner类型(继承自虚引用)的对象会有额外的处理:在其指向的对象被回收时,会调用clean方法,该方法主要是用来做对应的资源回收,在堆外内存DirectByteBuffer中就是用Cleaner进行堆外内存的回收,这也是虚引用在java中的典型应用。
看完了Reference的实现,再看看几个实现类里,各自有什么不同。
SoftReferencepublicclassSoftReference<T>extendsReference<T>{staticprivatelongclock;privatelongtimestamp;publicSoftReference(Treferent){super(referent);this.timestamp=clock; }publicSoftReference(Treferent,ReferenceQueue<?superT>q){super(referent,q);this.timestamp=clock; }publicTget(){ To=super.get();if(o!=null&&this.timestamp!=clock)this.timestamp=clock;returno; } }
软引用的实现很简单,就多了两个字段:clock和timestamp。clock是个静态变量,每次GC时都会将该字段设置成当前时间。timestamp字段则会在每次调用get方法时将其赋值为clock(如果不相等且对象没被回收)。
那这两个字段的作用是什么呢?这和软引用在内存不够的时候才被回收,又有什么关系呢?
这些还得看JVM的源码才行,因为决定对象是否需要被回收都是在GC中实现的。size_tReferenceProcessor::process_discovered_reflist( DiscoveredListrefs_lists[], ReferencePolicy*policy,boolclear_referent, BoolObjectClosure*is_alive, OopClosure*keep_alive, VoidClosure*complete_gc, AbstractRefProcTaskExecutor*task_executor) { ...//还记得上文提到过的DiscoveredList吗?refs_lists就是DiscoveredList。 //对于DiscoveredList的处理分为几个阶段,SoftReference的处理就在第一阶段 ...for(uinti=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){DiscoveredListIteratoriter(refs_list,keep_alive,is_alive);//Decidewhichsoftlyreachablerefsshouldbekeptalive. while(iter.has_next()){ iter.load_ptrs(DEBUG_ONLY(!discovery_is_atomic()/*allow_null_referent*/));//判断引用的对象是否存活 boolreferent_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("Droppingreference("INTPTR_FORMAT":%s"")bypolicy", (void*)iter.obj(),iter.obj()->klass()->internal_name()); }//RemoveReferenceobjectfromlist iter.remove();//MaketheReferenceobjectactiveagain iter.make_active();//keepthereferentaround 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方法则是完全相同:boolLRUMaxHeapPolicy::should_clear_reference(oopp, jlongtimestamp_clock){ jlonginterval=timestamp_clock-java_lang_ref_SoftReference::timestamp(p); assert(interval>=0,"Sanitycheck");//Theintervalwillbezeroiftherefwasaccessedsincethelastscavenge/gc. if(interval<=_max_interval){returnfalse; }returntrue; }
timestamp_clock就是SoftReference的静态字段clock,
java_lang_ref_SoftReference::timestamp(p)对应是字段timestamp。如果上次GC后有调用SoftReference#get,interval值为0,否则为若干次GC之间的时间差。
_max_interval则代表了一个临界值,它的值在LRUCurrentHeapPolicy和LRUMaxHeapPolicy两种策略中有差异。voidLRUCurrentHeapPolicy::setup(){ _max_interval=(Universe::get_heap_free_at_last_gc()/M)*SoftRefLRUPolicyMSPerMB; assert(_max_interval>=0,"Sanitycheck"); }voidLRUMaxHeapPolicy::setup(){size_tmax_heap=MaxHeapSize; max_heap-=Universe::get_heap_used_at_last_gc(); max_heap/=M; _max_interval=max_heap*SoftRefLRUPolicyMSPerMB; assert(_max_interval>=0,"Sanitycheck"); }
其中SoftRefLRUPolicyMSPerMB默认为1000,前者的计算方法和上次GC后可用堆大小有关,后者计算方法和(堆大小-上次gc时堆使用大小)有关。
看到这里你就知道SoftReference到底什么时候被被回收了,它和使用的策略(默认应该是LRUCurrentHeapPolicy),堆可用大小,该SoftReference上一次调用get方法的时间都有关系。WeakReferencepublicclassWeakReference<T>extendsReference<T>{publicWeakReference(Treferent){super(referent); }publicWeakReference(Treferent,ReferenceQueue<?superT>q){super(referent,q); } }
可以看到WeakReference在Java层只是继承了Reference,没有做任何的改动。那referent字段是什么时候被置为null的呢?要搞清楚这个问题我们再看下上文提到过的
process_discovered_reflist方法:size_tReferenceProcessor::process_discovered_reflist( DiscoveredListrefs_lists[],ReferencePolicy*policy,boolclear_referent,BoolObjectClosure*is_alive,OopClosure*keep_alive,VoidClosure*complete_gc,AbstractRefProcTaskExecutor*task_executor) { ...//Phase1:将所有不存活但是还不能被回收的软引用从refs_lists中移除(只有refs_lists为软引用的时候,这里policy才不为null) if(policy!=NULL){if(mt_processing){RefProcPhase1Taskphase1(*this,refs_lists,policy,true/*marks_oops_alive*/);task_executor->execute(phase1); }else{for(uinti=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,"Policymustbespecifiedforsoftreferences."); }//Phase2: //移除所有指向对象还存活的引用 if(mt_processing){RefProcPhase2Taskphase2(*this,refs_lists,!discovery_is_atomic()/*marks_oops_alive*/);task_executor->execute(phase2); }else{for(uinti=0;i<_max_num_q;i++){process_phase2(refs_lists[i],is_alive,keep_alive,complete_gc); } }//Phase3: //根据clear_referent的值决定是否将不存活对象回收 if(mt_processing){RefProcPhase3Taskphase3(*this,refs_lists,clear_referent,true/*marks_oops_alive*/);task_executor->execute(phase3); }else{for(uinti=0;i<_max_num_q;i++){process_phase3(refs_lists[i],clear_referent, is_alive,keep_alive,complete_gc); } }returntotal_list_count; }voidReferenceProcessor::process_phase3(DiscoveredList&refs_list,boolclear_referent,BoolObjectClosure*is_alive,OopClosure*keep_alive,VoidClosure*complete_gc){ResourceMarkrm;DiscoveredListIteratoriter(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){//NULLoutreferentpointer //将Reference的referent字段置为null,之后会被GC回收 iter.clear_referent(); }else{//keepthereferentaround //标记引用的对象为存活,该对象在这次GC将不会被回收 iter.make_referent_alive(); } ... } ... }
不管是弱引用还是其他引用类型,将字段referent置null的操作都发生在process_phase3中,而具体行为是由clear_referent的值决定的。而clear_referent的值则和引用类型相关。ReferenceProcessorStatsReferenceProcessor::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 //Softreferences size_tsoft_count=0; {GCTraceTimett("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();//Weakreferences size_tweak_count=0; {GCTraceTimett("WeakReference",trace_time,false,gc_timer); weak_count= process_discovered_reflist(_discoveredWeakRefs,NULL,true, is_alive,keep_alive,complete_gc,task_executor); }//Finalreferences size_tfinal_count=0; {GCTraceTimett("FinalReference",trace_time,false,gc_timer); final_count= process_discovered_reflist(_discoveredFinalRefs,NULL,false, is_alive,keep_alive,complete_gc,task_executor); }//Phantomreferences size_tphantom_count=0; {GCTraceTimett("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。PhantomReferencepublicclassPhantomReference<T>extendsReference<T>{publicTget(){returnnull; }publicPhantomReference(Treferent,ReferenceQueue<?superT>q){super(referent,q); } }
可以看到虚引用的get方法永远返回null,我们看个demo。publicstaticvoiddemo()throwsInterruptedException{Objectobj=newObject(); ReferenceQueue<Object>refQueue=newReferenceQueue<>(); PhantomReference<Object>phanRef=newPhantomReference<>(obj,refQueue);Objectobjg=phanRef.get();//这里拿到的是null System.out.println(objg);//让obj变成垃圾 obj=null; System.gc(); Thread.sleep(3000);//gc后会将phanRef加入到refQueue中 Reference<?extendsObject>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来实现堆外内存回收的,后续会写篇文章来说说堆外内存的里里外外。
哈文更博(哈文更博,她不是永远的咏嫂)哈文更博(哈文更博,她不是永远的咏嫂)石狮市霖豪网络科技有限公司李咏去世后,哈文一段时间未曾更新网络社交媒体动态。直到最近其微博更文,只一个字早。吃瓜群众于是激动,下面留言很多有励
严歌苓芳华(严歌苓为什么要写芳华?)严歌苓芳华(严歌苓为什么要写芳华?)读书最近我写了两篇关于芳华的文章,有个别人在评论区说这部作品是伤痕文学,只有心里阴暗的人才能写得出来还有的人说作家严歌苓故意贩卖人性弱点,让人看
体力不济(生活中经常体力不支)体力不济(生活中经常体力不支)很容易体力不支,如何提升体力?要想提升体力,首先,我们要搞清楚,我们说的体力到底是一个什么东西。能搬起很重的东西,是体力好吗?能跑步很远的距离,是体力
触目惊心(触目惊心!一辆货车挤满18人)触目惊心(触目惊心!一辆货车挤满18人)收录于话题货运车辆载人这是一种非常危险的行为存在极大的交通安全隐患一旦发生事故后果不堪设想可是总有人心怀侥幸触目惊心!小货车挤满18人6月1
王力宏2012春晚(王力宏李云迪事件是怎么回事)王力宏2012春晚(王力宏李云迪事件是怎么回事)前段时间王力宏发布了一份律师声明,这份声明不仅否认了王力宏李云迪的恋情,还澄清了当时穿的沸沸扬扬的怒扇刘谦一巴掌的事情。王力宏李云迪
王力宏章子怡(章子怡送女儿王力宏照片)王力宏章子怡(章子怡送女儿王力宏照片)今天是一年一度六一儿童节,不仅我们老百姓在为给宝宝们一个欢乐的儿童节而绞尽脑汁,娱乐圈的明星们也一样,志在为家庭那位小成员度过一个难忘的儿童节
走进科学观后感(盘点走进科学那些让你意想不到的结)走进科学观后感(盘点走进科学那些让你意想不到的结)走进科学是1998年开播的一个大型科普栏目,主要还是为我们扫盲,让我们更加地了解科学。但是也有很多故事中的结局,让人捧腹大笑,惊讶
取消资格(考生考上清华却被取消资格)取消资格(考生考上清华却被取消资格)高考,是一场公平公正的考试,容不得投机取巧。众所周知,我国的高考目前是统一考试,分省录取的模式,而这样的模式,必然存在地域性的录取差异。而也正是
甲壳虫help(甲壳虫乐队风靡英国之谜)甲壳虫help(甲壳虫乐队风靡英国之谜)在55年前的今天,1963年1月22日(农历腊月廿七),甲壳虫乐队开始风靡英国。甲壳虫乐队的由来1955年,约翰列侬,早前在艺术学校读书,在
屏幕刷新率多少合适(屏幕刷新率是什么意思?)很多玩家在选择电竞显示器的时候只看重刷新率,但是刷新率越高的电竞显示器就越好吗?对也不对。因为虽然刷新率是一个重要参数,但还有很多参数会影响电竞显示器的使用体验,下面我们就来聊一聊
雪中飞羽绒服男款(4款羽绒服充绒量等未达标准)雪中飞羽绒服男款(4款羽绒服充绒量等未达标准)保暖防寒冬天保暖,怎能少得了羽绒服。然而,市面上销售的羽绒服质量如何?近日,佛山市消委会官网发布羽绒服质量调查分析报告。分析报告显示,
咏雪诗句(8首咏雪诗词)咏雪诗句(8首咏雪诗词)冬日已至,大雪节气来了。冬日里,最令人期待的,就是雪了。当雪落时,天地一片洁白,文人墨客争相吟咏,留下许多杰作。今天,诗词君整理了8首咏雪诗词,一直来看看诗
情人节的诗句(情人节用古诗词表白)情人节的诗句(情人节用古诗词表白)大家好,我是诗语,情人节到了,您有合适的表白方式吗?如果没有,请您收下小编为您整理的表白古诗词吧!1有一美人兮,见之不忘。一日不见兮,思之如狂。出
华为5g手机开售(外媒拆解华为5G手机)华为5g手机开售(外媒拆解华为5G手机)近日,有媒体拆解了华为5G智能手机Mate40E,并针对其使用的零件进行了一系列分析,经分析后发现,在这款手机中,国产零件比例按金额计算达到
期中考试后的反思(陪读老妈的反思)期中考试后的反思(陪读老妈的反思)今天期中考试的成绩下来了。儿子考试的成绩并不理想,远远低于我的意料之外。儿子是属于那种比较优秀的学生。做作业的速度特别快,思维敏捷。在1年级到5年
chm电子书(教你如何快速简单的制作CHM电子书籍)chm电子书(教你如何快速简单的制作CHM电子书籍)好久没写文,我都快忘了。这次回来教大家如何制作CHM格式的电子书。众所周知,现在这类型的格式基本已经不吃香因为很少有人在用了。虽
携程vip贵宾卡(上海联通携程大王卡)携程vip贵宾卡(上海联通携程大王卡)抖音一刷就废寝忘食,流量一算已经用超很多想与家人朋友随时视频的你,是否每天还要细数流量还剩余多少?在我国互联网进入移动互联网时代之后,年轻用户
wayos安装(WayOS路由PPPoE设置)wayos安装(WayOS路由PPPoE设置)当广域网无法兼顾既要通过同一个用户前置接入设备连接远程的多个用户主机,又要提供类似拨号一样的接入控制计费等功能,而且要尽可能地减少用户
天河哪里有人帮做网贷(专业帮人做网贷的联系方式)29岁的方云,被称为各位网贷中介鼻祖。他是最早进入网贷行业的中介之一,并开创了众多网贷平台的贷款技术,网贷中介又称撸口子从业者。他最早进行中介培训,线上线下教撸口子的技术,教出上千
人生何处不绽放(一根拐杖走天下,人生何处不绽放?)人生何处不绽放(一根拐杖走天下,人生何处不绽放?)一根拐杖走天下,为狼哥助力加油!和狼哥(杨其湘)相识好几年了,这些年来时常有些工作和生活上的交集。平日里忙忙碌碌,彼此之间虽没有太
老人持拐打死老伴(86岁老人用拐杖打死老伴)老人持拐打死老伴(86岁老人用拐杖打死老伴)就因为妻子没做一顿午饭,86岁的孙某用拐棍将一起生活了50多年的83岁的妻子郭女士打死。因为孙某下肢骨折下不了地,今天上午,市一中院法官
哪里有寿司卖(什么地方有寿司买)长年占据日本寿司店第一,是一家创建于1984年的老牌连锁寿司店,拥有超550家的店铺,在日本可以说是无人不知无人不晓了。这家店以鱼肉新鲜出名,一直严格把控食材品质。不管别家店使出1