引用类型(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来实现堆外内存回收的,后续会写篇文章来说说堆外内存的里里外外。
博弈论案例分析(基本概念解释以及具体案例分析)博弈论案例分析(基本概念解释以及具体案例分析)博弈论也也称为对策论或赛局理论,是研究具有斗争或竞争性质现象的数学理论和方法。博弈论考虑游戏中的个体的预测行为和实际行为,并研究它们的
韩国恐怖片推荐(绝对不敢一人看?)韩国恐怖片推荐(绝对不敢一人看?)客人导演金光泰编剧金光泰主演柳承龙李星民李准千禹熙类型恐怖惊悚悬疑人性地区韩国语言韩语上映20150709(韩国)片长107分钟又名ThePipe
bitcomet比特彗星(BitCometv1。56。3比特彗星下载软件)bitcomet比特彗星(BitCometv1。56。3比特彗星下载软件)BitComet(比特彗星)是一个完全免费的BitTorrent(BT)下载管理软件,也称BT下载客户端,
边疆行吴丹(踏上边疆行的旅程)边疆行吴丹(踏上边疆行的旅程)半年前,我还是苏州电视台的一名新闻主播,每天穿着套装端坐在主播台前,相对于电视内容,我更关注的是播音技巧和我自己的屏幕形象?踏上边疆行的旅程,更让我觉
帕拉迪诺(美国知名私家侦探帕拉迪诺去世)帕拉迪诺(美国知名私家侦探帕拉迪诺去世)美国全国广播公司(NBC)2月2日消息,据亲属和警方证实,美国知名私家侦探杰克帕拉迪诺(JackPalladino)在遭遇抢劫中头部受伤后,
汗血马价格(汗血马为什么这么珍贵)汗血马价格(汗血马为什么这么珍贵)世界上有很多名马,在已知的世界十大名马中,就有赤兔马汗血宝马等,那么赤兔马是不是汗血马,二者是同一种马吗,另外一个大家关心的问题就是汗血马为什么这
跑江湖生意(常见的几种地摊火爆模式)跑江湖生意(常见的几种地摊火爆模式)在确定了经营的产品之后,接下来就是模式的选择了,我们只有先弄清地摊上常有的模式有哪些及其特点,才能更好的选择适合我们的推广模式,一般说来地菜有几
异氰酸酯(异氰酸酯的主要种类分析)异氰酸酯(异氰酸酯的主要种类分析)异氰酸酯是一种有机物,通过多次的研究改进后,异氰酸酯可以用来合成聚氨酯的材料,现在的合成方法应比较的成熟了,有很多种方法都可以用来合成异氰酸酯,异
户型分析(全户型分析及最新进展曝光!)户型分析(全户型分析及最新进展曝光!)一个神秘的二环限竞房项目,一度传言会内部消化,就在近期有官方答复称,要本着公平公正公开的原则进行销售,让大家看到了一线希望。这个项目火是必然的
迄今最小恐龙(惊!已知最小的恐龙)迄今最小恐龙(惊!已知最小的恐龙)古生物学家曾以为,一块被琥珀包裹的化石属于一只蜂鸟大小的恐龙。现在,它被重新归类为一种大眼睛的蜥蜴。大约9900万年前,在今天的缅甸,树脂渗了出来
中国柳编网(非遗柳编传承,人才辈出)中国柳编网(非遗柳编传承,人才辈出)解印权编织技艺解印权,1981年3月出生于山东省临沭县白旄镇,汉族,中专学历。自幼随父,母,哥,姐学习柳编编织,完成学业后拜省工艺美术大师杨进邦
保险行销(34岁保险行销员!)保险行销(34岁保险行销员!)34岁的基尔(KeithGill)长发及肩,在美国贴吧Reddit版上被称为DeepFingValue,2岁女儿叫他Dada。再平凡不过的男子,却在论
中国平安保险倒闭(中国平安真的不平安了吗?)中国平安保险倒闭(中国平安真的不平安了吗?)2020年12月1日中国平安股价创出91。65元的新高,随后开始了长达8个月的回调,7月14日更是跌至57。91元,最大跌幅达到了36。
太极扇第一套(武式太极扇)太极扇第一套(武式太极扇)难得一见的实战扇法一一武式太极扇李新方噫吁戏!中国武术,至广大而极精微!微独门派众多,各尽其妙,而器械尤异彩纷呈,夙有十八般兵器之称,且十八之外复多奇兵,
42式太极拳分解教学(42式太极拳攻防用法图解)42式太极拳分解教学(42式太极拳攻防用法图解)2单鞭攻防用法以左单鞭势为例,左手动作为防守反击方法,右勾手防守。甲方右脚向前上步,用右掌向乙方胸部推击(图512)。图512乙方右
无限制格斗术(无限制的搏击格斗招式,简单实用,容易学)无限制格斗术(无限制的搏击格斗招式,简单实用,容易学)今天功夫武术酷为大家介绍4招江湖防身术。一老夫劈柴敌方上右步以右摆拳进攻我方左侧头部我方立即上右步并以右手反抓敌方右手腕,同时
穿墙术揭秘(迷你世界神奇的穿墙术)穿墙术揭秘(迷你世界神奇的穿墙术)迷你世界之大,无奇不有,但是你知道在迷你世界里还有一些非常隐蔽的小秘密吗?如果不是身经百战,在游戏里摸爬滚打多年的老玩家,可能真的很难发现这些小技
断案高手(历史三大断案高手)断案高手(历史三大断案高手)现如今,警察的侦破设备已经非常先进了,那么在古代时,没有现如今的侦察设备,古代有没有断案如神的人?这肯定是有的,最为知名的就是狄仁杰和包青天了。可是除了
静静怎么死的(33岁张静静离世)静静怎么死的(33岁张静静离世)都说月有阴晴圆缺,人有悲欢离合,道理人人都懂,但在死亡面前,该痛心的照样痛心,该伤心的照样流泪,尤其是英雄的逝世,更让人难过。出生于1987年的张静
民族风歌词(男人再强也要有个家)民族风歌词(男人再强也要有个家)男人再强也要有个家作者秋实日月交替永在天道下寒来暑往屡应接不暇时光任由风雨的洗刷岁月无情淘不尽浮华朝夕艳阳常会伴红霞绿叶配着满枝的鲜花男人再强也要有
玛格丽特鸡尾酒(自制春光鸡尾酒,你也可以)玛格丽特鸡尾酒(自制春光鸡尾酒,你也可以)如果要让你用一种水果来形容阳光带给你的感觉的话,那么柑橘类的水果很可能是第一时间出现在你脑海里的答案。明媚的亮橘色明亮而高扬的香气鲜甜多汁
玛格丽特花(登录玛格丽特花生虫了怎么办?)玛格丽特花(登录玛格丽特花生虫了怎么办?)玛格丽特是一种特别皮实的花卉,养护起来非常的容易,但是在没有照顾好的时候,也会生病虫害,常见的就是蚜虫和潜叶蝇。蚜虫的图片可能会引起不适,