范文健康探索娱乐情感热点
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文

引用类型(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来实现堆外内存回收的,后续会写篇文章来说说堆外内存的里里外外。

山西洪洞大槐树王氏(寻根祭祖大槐树天下王氏出太原)山西洪洞大槐树王氏(寻根祭祖大槐树天下王氏出太原)明朝时期的人口大迁移,带给了我们多少年的寻根祭祖,而山西洪洞大槐树承载了多少人的根祖故事。在山西旅游线路中,晋南的根祖文化线路吸引雪佛兰怎么样(雪佛兰质量怎么样值得买吗)雪佛兰汽车是许多消费者忽略的一个品牌,但是在合资品牌中雪佛兰的性价比非常高。套用大家的话那就是国产车的价格,合资车的身份。雪佛兰目前最火爆的车型当属探界者与科鲁泽,其次是迈锐宝以及理肤泉喷雾怎么样?(理肤泉舒缓喷雾)理肤泉喷雾怎么样?(理肤泉舒缓喷雾)理肤泉舒缓喷雾舒缓修护,适用于多种肌肤的状态,焕发新肌。理肤泉是欧莱雅旗下的护肤品牌,主打温泉水护肤,理肤泉这个名字,来自于它的发源地,盛产治疗路亚钓法(钓氏告诉你路亚钓鱼到底应该怎么玩)路亚钓法(钓氏告诉你路亚钓鱼到底应该怎么玩)钓鱼有许多种玩法,国内比较常见的就是传统钓,台钓,那么除了这两种钓法以外,还有筏钓,抛竿,路亚等方式。今天我们就主要介绍一下路亚钓法。首家用缝纫机哪种好用(家用型缝纫机哪个品牌比较好)家用缝纫机哪种好用(家用型缝纫机哪个品牌比较好)目前我们经常可以见到小型缝纫机出现在市场上,它是能给人们缝纫衣服带来方便,不会跟过去的缝纫机一样很难用,家用型缝纫机哪个品牌比较好?一键刷机哪个好(安卓手机一键刷机用什么软件最好用)一键刷机哪个好(安卓手机一键刷机用什么软件最好用)谈起安卓刷机,很多人都不陌生,在这个开源的系统上,用户各种刷机,我们最常见的,小白接触最多的,也就是一键刷机了,到底安卓手机应不应成都重庆哪个(成都和重庆中间游玩)5月19日是中国旅游日,川渝两地同步启动了巴蜀文化旅游走廊自由行活动,重庆和四川的百余家旅游景区相互分别提供了60万张免费门票,为川渝游客带来福利。那你知道,重庆人最爱的四川旅游目k线图图解(K线组合图解大全)k线图图解(K线组合图解大全)大家好,这节课我们为大家讲一下K线组合组成的一些形态。之前讲,有至少两根才能形成一个K线组合,单木不成林,至少两根,一般两根形成形态,三根形成趋势,这k线身怀六甲(最美的常见k线形态身怀六甲)k线身怀六甲(最美的常见k线形态身怀六甲)什么是孕线形态孕线是由两根K线组成,前一根K线的实体较长,可以是阴线,也可以是阳线。后一根K线的实体相对前一根K线来说要短一些,且后一根K半年线是多少日(半年线是多少日线股票半年线怎么看)半年线是多少日(半年线是多少日线股票半年线怎么看)半年线是多少日线,股票半年线怎么看?在K线图中,移动平均线有多条,其中有一条叫做股票半年线。股票半年线指的是120日移动平均线。半股票k线图经典图解(K线图经典图解)股票k线图经典图解(K线图经典图解)K线能够全面透彻地观察到市场的真正变化。我们从K线图中,既可看到股价(或大市)的趋势,也同时可以了解到每日市况的波动情形。很多股民表示,K线图时
刘涛丈夫(刘涛与王珂公开秀恩爱)刘涛丈夫(刘涛与王珂公开秀恩爱)过往总是和老公王珂在综艺里秀恩爱,刘涛自从为蒋勤勤与陈建斌秦海璐和王新军等明星夫妇当上导游后,在一堆高甜糖里却始终不提王珂。近年来频繁被曝婚姻亮红灯萧红呼兰河传(把旧社会底层农村的病态,写得入木三分)萧红呼兰河传(把旧社会底层农村的病态,写得入木三分)踏实姐202105191124萧红的呼兰河传写的都是她童年的时候,在家乡难以忘怀的一些人和事,反映的是旧社会底层人们的生活艰辛。萧红的作品(走,去萧红的世界)萧红的作品(走,去萧红的世界)整理雨驿萧红萧红3岁和母亲萧红与萧军萧红与端木蕻良主题纪念萧红诞辰110周年暨萧红大传新书分享会时间2021年6月5日1000地点首都图书馆A座报告厅表哥我来也(表哥也来蹭热度)表哥我来也(表哥也来蹭热度)夺冠2021奥运国球女单冠军陈梦夺冠那刻,演员黄晓明发文庆祝,不可以么?完全可以,我觉得。在这一刻,黄晓明是陈梦的家人。自己家里面出了代表国家,参赛奥运岳飞儿子(岳飞被杀后,他冒死救下岳飞儿子)岳飞儿子(岳飞被杀后,他冒死救下岳飞儿子)兵者,国之大事,死生之地,存亡之道,不可不察也。孙子兵法在兵法中,最讲究的有五个要素道天地将法。在屈辱的南宋历史中,名气最大的将就是岳飞了岳飞弟弟(岳飞弟弟岳翻)岳飞弟弟(岳飞弟弟岳翻)杨铁心把那枪使发了,招数灵动,变幻巧妙。但那道人身随枪走,趋避进退,却那里刺得着他半分?七十二路杨家枪法堪堪使完,杨铁心不禁焦躁,倒提铁枪,回身便走,那道人好看的卡通电影(推荐10部必看的动漫电影)好看的卡通电影(推荐10部必看的动漫电影)不知道在家的小伙伴有没有感觉很无聊啊,是不是不出门,窝在家里追剧呢?不过这多时间要是浪费了怪可惜的,所以今天小编就给小伙伴推荐10部好看的如何扩大c盘空间(教你如何无损扩展C盘空间大小)如何扩大c盘空间(教你如何无损扩展C盘空间大小)电脑用久了,因为一开始预留空间不足就会出现C盘不够用的情况,而且每次打开我的电脑看到红色的条条也很烦人。这时候数据都存储了很多,软件用工合同(什么是无固定期限劳动合同?)用工合同(什么是无固定期限劳动合同?)近期,小编收到了不少网友的咨询,其中网友梦轩萍缘问到请问什么是无固定期限劳动合同?什么条件下才能签无固定期限合同?如果到了可以签无固定期限的时名人故事50字(励志的小故事简短50字5则)名人故事50字(励志的小故事简短50字5则))7励志的小故事简短50字5则成功的花,人们只惊慕她现时的明艳!然而当初她的芽儿,浸透了奋斗的泪泉,洒遍了牺牲的血雨!冰心。励志故事网的盒子论坛(电视盒子什么牌子好?)盒子论坛(电视盒子什么牌子好?)关于电视盒子,现在市面上卖的比较火的其实来来去去也就那么几个头部的牌子,根据我自己多年的专业论坛潜水经验,今天就给大家说说电视盒子什么牌子好!1泰捷