面试中经常被问到Java引用类型原理,带你深入剖析
1。选择唯一性索引
唯一性索引的值是唯一的,可以更快速的通过该索引来确定某条记录。例如,学生表中学号是具有唯一性的字段。为该字段建立唯一性索引可以很快的确定某个学生的信息。如果使用姓名的话,可能存在同名现象,从而降低查询速度。2。为经常需要排序、分组和联合操作的字段建立索引
经常需要ORDERBY、GROUPBY、DISTINCT和UNION等操作的字段,排序操作会浪费很多时间。如果为其建立索引,可以有效地避免排序操作。3。为常作为查询条件的字段建立索引
如果某个字段经常用来做查询条件,那么该字段的查询速度会影响整个表的查询速度。因此,为这样的字段建立索引,可以提高整个表的查询速度。4。限制索引的数目
索引的数目不是越多越好。每个索引都需要占用磁盘空间,索引越多,需要的磁盘空间就越大。修改表时,对索引的重构和更新很麻烦。越多的索引,会使更新表变得很浪费时间。5。尽量使用数据量少的索引
如果索引的值很长,那么查询的速度会受到影响。例如,对一个CHAR(100)类型的字段进行全文检索需要的时间肯定要比对CHAR(10)类型的字段需要的时间要多。6。尽量使用前缀来索引
如果索引字段的值很长,最好使用值的前缀来索引。例如,TEXT和BLOG类型的字段,进行全文检索会很浪费时间。如果只检索字段的前面的若干个字符,这样可以提高检索速度。7。删除不再使用或者很少使用的索引
表中的数据被大量更新,或者数据的使用方式被改变后,原有的一些索引可能不再需要。数据库管理员应当定期找出这些索引,将它们删除,从而减少索引对更新操作的影响。8。最左前缀匹配原则,非常重要的原则。
mysql会一直向右匹配直到遇到范围查询(、、between、like)就停止匹配,比如a1andb2c3andd4如果建立(a,b,c,d)顺序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,a,b,d的顺序可以任意调整。9。和in可以乱序。
比如a1andb2andc3建立(a,b,c)索引可以任意顺序,mysql的查询优化器会帮你优化成索引可以识别的形式10。尽量选择区分度高的列作为索引。
区分度的公式是count(distinctcol)count(),表示字段不重复的比例,比例越大我们扫描的记录数越少,唯一键的区分度是1,而一些状态、性别字段可能在大数据面前区分度就是0,那可能有人会问,这个比例有什么经验值吗?使用场景不同,这个值也很难确定,一般需要join的字段我们都要求是0。1以上,即平均1条扫描10条记录11。索引列不能参与计算,保持列干净。
比如fromunixtime(createtime)’20140529’就不能使用到索引,原因很简单,b树中存的都是数据表中的字段值,但进行检索时,需要把所有元素都应用函数才能比较,显然成本太大。所以语句应该写成createtimeunixtimestamp(’20140529’);12。尽量的扩展索引,不要新建索引。
比如表中已经有a的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可
注意:选择索引的最终目的是为了使查询的速度变快。上面给出的原则是最基本的准则,但不能拘泥于上面的准则。读者要在以后的学习和工作中进行不断的实践。根据应用的实际情况进行分析和判断,选择最合适的索引方式。
Java中一共有4种引用类型(其实还有一些其他的引用类型比如FinalReference):强引用、软引用、弱引用、虚引用。
其中强引用就是我们经常使用的ObjectanewObject();这样的形式,在Java中并没有对应的Reference类。
本篇文章主要是分析软引用、弱引用、虚引用的实现,这三种引用类型都是继承于Reference这个类,主要逻辑也在Reference中。问题
在分析前,先抛几个问题?
1。网上大多数文章对于软引用的介绍是:在内存不足的时候才会被回收,那内存不足是怎么定义的?什么才叫内存不足?
2。网上大多数文章对于虚引用的介绍是:形同虚设,虚引用并不会决定对象的生命周期。主要用来跟踪对象被垃圾回收器回收的活动。真的是这样吗?
3。虚引用在Jdk中有哪些场景下用到了呢?Reference
我们先看下Reference。java中的几个字段publicabstractclassReferenceT{引用的对象privateTreferent;回收队列,由使用者在Reference的构造函数中指定volatileReferenceQueuelt;?superTqueue;当该引用被加入到queue中的时候,该字段被设置为queue中的下一个元素,以形成链表结构volatileReferencenext;在GC时,JVM底层会维护一个叫DiscoveredList的链表,存放的是Reference对象,discovered字段指向的就是链表中的下一个元素,由JVM设置transientprivateReferenceTdiscovered;进行线程同步的锁对象staticprivateclassLock{}privatestaticLocklocknewLock();等待加入queue的Reference对象,在GC时由JVM设置,会有一个java层的线程(ReferenceHandler)源源不断的从pending中提取元素加入到queueprivatestaticReferenceObjectpendingnull;}
一个Reference对象的生命周期如下:
主要分为Native层和Java层两个部分。
Native层在GC时将需要被回收的Reference对象加入到DiscoveredList中(代码在referenceProcessor。cpp中processdiscoveredreferences方法),然后将DiscoveredList的元素移动到PendingList中(代码在referenceProcessor。cpp中enqueuediscoveredrefhelper方法),PendingList的队首就是Reference类中的pending对象。
看看Java层的代码privatestaticclassReferenceHandlerextendsThread{。。。publicvoidrun(){while(true){tryHandlePending(true);}}}staticbooleantryHandlePending(booleanwaitForNotify){ReferenceObjectr;Cleanerc;try{synchronized(lock){if(pending!null){rpending;如果是Cleaner对象,则记录下来,下面做特殊处理crinstanceofCleaner?(Cleaner)r:null;指向PendingList的下一个对象pendingr。discovered;r。discoverednull;}else{如果pending为null就先等待,当有对象加入到PendingList中时,jvm会执行notifyif(waitForNotify){lock。wait();}retryifwaitedreturnwaitForNotify;}}}。。。如果时CLeaner对象,则调用clean方法进行资源回收if(c!null){c。clean();returntrue;}将Reference加入到ReferenceQueue,开发者可以通过从ReferenceQueue中poll元素感知到对象被回收的事件。ReferenceQueuelt;?superObjectqr。queue;if(q!ReferenceQueue。NULL)q。enqueue(r);returntrue;}
流程比较简单:就是源源不断的从PendingList中提取出元素,然后将其加入到ReferenceQueue中去,开发者可以通过从ReferenceQueue中poll元素感知到对象被回收的事件。
另外需要注意的是,对于Cleaner类型(继承自虚引用)的对象会有额外的处理:在其指向的对象被回收时,会调用clean方法,该方法主要是用来做对应的资源回收,在堆外内存DirectByteBuffer中就是用Cleaner进行堆外内存的回收,这也是虚引用在java中的典型应用。
看完了Reference的实现,再看看几个实现类里,各自有什么不同。
SoftReferencepublicclassSoftReferenceTextendsReferenceT{staticprivatelongclock;privatelongtimestamp;publicSoftReference(Treferent){super(referent);this。timestampclock;}publicSoftReference(Treferent,ReferenceQueuelt;?superTq){super(referent,q);this。timestampclock;}publicTget(){Tosuper。get();if(o!nullthis。timestamp!clock)this。timestampclock;returno;}}
软引用的实现很简单,就多了两个字段:clock和timestamp。clock是个静态变量,每次GC时都会将该字段设置成当前时间。timestamp字段则会在每次调用get方法时将其赋值为clock(如果不相等且对象没被回收)。
那这两个字段的作用是什么呢?这和软引用在内存不够的时候才被回收,又有什么关系呢?
这些还得看JVM的源码才行,因为决定对象是否需要被回收都是在GC中实现的。sizetReferenceProcessor::processdiscoveredreflist(DiscoveredListrefslists〔〕,ReferencePolicypolicy,boolclearreferent,BoolObjectClosureisalive,OopClosurekeepalive,VoidClosurecompletegc,AbstractRefProcTaskExecutortaskexecutor){。。。还记得上文提到过的DiscoveredList吗?refslists就是DiscoveredList。对于DiscoveredList的处理分为几个阶段,SoftReference的处理就在第一阶段。。。for(uinti0;imaxnumq;i){processphase1(refslists〔i〕,policy,isalive,keepalive,completegc);}。。。}该阶段的主要目的就是当内存足够时,将对应的SoftReference从refslist中移除。voidReferenceProcessor::processphase1(DiscoveredListrefslist,ReferencePolicypolicy,BoolObjectClosureisalive,OopClosurekeepalive,VoidClosurecompletegc){DiscoveredListIteratoriter(refslist,keepalive,isalive);Decidewhichsoftlyreachablerefsshouldbekeptalive。while(iter。hasnext()){iter。loadptrs(DEBUGONLY(!discoveryisatomic()allownullreferent));判断引用的对象是否存活boolreferentisdead(iter。referent()!NULL)!iter。isreferentalive();如果引用的对象已经不存活了,则会去调用对应的ReferencePolicy判断该对象是不时要被回收if(referentisdead!policyshouldclearreference(iter。obj(),softreftimestampclock)){if(TraceReferenceGC){gclogorttyprintcr(Droppingreference(INTPTRFORMAT:s)bypolicy,(void)iter。obj(),iter。obj()klass()internalname());}RemoveReferenceobjectfromlistiter。remove();MaketheReferenceobjectactiveagainiter。makeactive();keepthereferentarounditer。makereferentalive();iter。movetonext();}else{iter。next();}}。。。}
refslists中存放了本次GC发现的某种引用类型(虚引用、软引用、弱引用等),而processdiscoveredreflist方法的作用就是将不需要被回收的对象从refslists移除掉,refslists最后剩下的元素全是需要被回收的元素,最后会将其第一个元素赋值给上文提到过的Reference。javapending字段。
ReferencePolicy一共有4种实现:NeverClearPolicy,AlwaysClearPolicy,LRUCurrentHeapPolicy,LRUMaxHeapPolicy。
其中NeverClearPolicy永远返回false,代表永远不回收SoftReference,在JVM中该类没有被使用,AlwaysClearPolicy则永远返回true,在referenceProcessor。hppsetup方法中中可以设置policy为AlwaysClearPolicy,至于什么时候会用到AlwaysClearPolicy,大家有兴趣可以自行研究。
LRUCurrentHeapPolicy和LRUMaxHeapPolicy的shouldclearreference方法则是完全相同:boolLRUMaxHeapPolicy::shouldclearreference(oopp,jlongtimestampclock){jlongintervaltimestampclockjavalangrefSoftReference::timestamp(p);assert(interval0,Sanitycheck);Theintervalwillbezeroiftherefwasaccessedsincethelastscavengegc。if(intervalmaxinterval){returnfalse;}returntrue;}
timestampclock就是SoftReference的静态字段clock,javalangrefSoftReference::timestamp(p)对应是字段timestamp。如果上次GC后有调用SoftReferenceget,interval值为0,否则为若干次GC之间的时间差。
maxinterval则代表了一个临界值,它的值在LRUCurrentHeapPolicy和LRUMaxHeapPolicy两种策略中有差异。voidLRUCurrentHeapPolicy::setup(){maxinterval(Universe::getheapfreeatlastgc()M)SoftRefLRUPolicyMSPerMB;assert(maxinterval0,Sanitycheck);}voidLRUMaxHeapPolicy::setup(){sizetmaxheapMaxHeapSize;maxheapUniverse::getheapusedatlastgc();maxheapM;maxintervalmaxheapSoftRefLRUPolicyMSPerMB;assert(maxinterval0,Sanitycheck);}
其中SoftRefLRUPolicyMSPerMB默认为1000,前者的计算方法和上次GC后可用堆大小有关,后者计算方法和(堆大小上次gc时堆使用大小)有关。
看到这里你就知道SoftReference到底什么时候被被回收了,它和使用的策略(默认应该是LRUCurrentHeapPolicy),堆可用大小,该SoftReference上一次调用get方法的时间都有关系。WeakReferencepublicclassWeakReferenceTextendsReferenceT{publicWeakReference(Treferent){super(referent);}publicWeakReference(Treferent,ReferenceQueuelt;?superTq){super(referent,q);}}
可以看到WeakReference在Java层只是继承了Reference,没有做任何的改动。那referent字段是什么时候被置为null的呢?要搞清楚这个问题我们再看下上文提到过的processdiscoveredreflist方法:sizetReferenceProcessor::processdiscoveredreflist(DiscoveredListrefslists〔〕,ReferencePolicypolicy,boolclearreferent,BoolObjectClosureisalive,OopClosurekeepalive,VoidClosurecompletegc,AbstractRefProcTaskExecutortaskexecutor){。。。Phase1:将所有不存活但是还不能被回收的软引用从refslists中移除(只有refslists为软引用的时候,这里policy才不为null)if(policy!NULL){if(mtprocessing){RefProcPhase1Taskphase1(this,refslists,policy,truemarksoopsalive);taskexecutorexecute(phase1);}else{for(uinti0;imaxnumq;i){processphase1(refslists〔i〕,policy,isalive,keepalive,completegc);}}}else{policyNULLassert(refslists!discoveredSoftRefs,Policymustbespecifiedforsoftreferences。);}Phase2:移除所有指向对象还存活的引用if(mtprocessing){RefProcPhase2Taskphase2(this,refslists,!discoveryisatomic()marksoopsalive);taskexecutorexecute(phase2);}else{for(uinti0;imaxnumq;i){processphase2(refslists〔i〕,isalive,keepalive,completegc);}}Phase3:根据clearreferent的值决定是否将不存活对象回收if(mtprocessing){RefProcPhase3Taskphase3(this,refslists,clearreferent,truemarksoopsalive);taskexecutorexecute(phase3);}else{for(uinti0;imaxnumq;i){processphase3(refslists〔i〕,clearreferent,isalive,keepalive,completegc);}}returntotallistcount;}voidReferenceProcessor::processphase3(DiscoveredListrefslist,boolclearreferent,BoolObjectClosureisalive,OopClosurekeepalive,VoidClosurecompletegc){ResourceMarkrm;DiscoveredListIteratoriter(refslist,keepalive,isalive);while(iter。hasnext()){iter。updatediscovered();iter。loadptrs(DEBUGONLY(falseallownullreferent));if(clearreferent){NULLoutreferentpointer将Reference的referent字段置为null,之后会被GC回收iter。clearreferent();}else{keepthereferentaround标记引用的对象为存活,该对象在这次GC将不会被回收iter。makereferentalive();}。。。}。。。}
不管是弱引用还是其他引用类型,将字段referent置null的操作都发生在processphase3中,而具体行为是由clearreferent的值决定的。而clearreferent的值则和引用类型相关。ReferenceProcessorStatsReferenceProcessor::processdiscoveredreferences(BoolObjectClosureisalive,OopClosurekeepalive,VoidClosurecompletegc,AbstractRefProcTaskExecutortaskexecutor,GCTimergctimer){NOTPRODUCT(verifyoktohandlereflists());。。。processdiscoveredreflist方法的第3个字段就是clearreferentSoftreferencessizetsoftcount0;{GCTraceTimett(SoftReference,tracetime,false,gctimer);softcountprocessdiscoveredreflist(discoveredSoftRefs,currentsoftrefpolicy,true,isalive,keepalive,completegc,taskexecutor);}updatesoftrefmasterclock();Weakreferencessizetweakcount0;{GCTraceTimett(WeakReference,tracetime,false,gctimer);weakcountprocessdiscoveredreflist(discoveredWeakRefs,NULL,true,isalive,keepalive,completegc,taskexecutor);}Finalreferencessizetfinalcount0;{GCTraceTimett(FinalReference,tracetime,false,gctimer);finalcountprocessdiscoveredreflist(discoveredFinalRefs,NULL,false,isalive,keepalive,completegc,taskexecutor);}Phantomreferencessizetphantomcount0;{GCTraceTimett(PhantomReference,tracetime,false,gctimer);phantomcountprocessdiscoveredreflist(discoveredPhantomRefs,NULL,false,isalive,keepalive,completegc,taskexecutor);}。。。}
可以看到,对于Softreferences和Weakreferencesclearreferent字段传入的都是true,这也符合我们的预期:对象不可达后,引用字段就会被置为null,然后对象就会被回收(对于软引用来说,如果内存足够的话,在Phase1,相关的引用就会从refslist中被移除,到Phase3时refslist为空集合)。
但对于Finalreferences和Phantomreferences,clearreferent字段传入的是false,也就意味着被这两种引用类型引用的对象,如果没有其他额外处理,只要Reference对象还存活,那引用的对象是不会被回收的。Finalreferences和对象是否重写了finalize方法有关,不在本文分析范围之内,我们接下来看看Phantomreferences。PhantomReferencepublicclassPhantomReferenceTextendsReferenceT{publicTget(){returnnull;}publicPhantomReference(Treferent,ReferenceQueuelt;?superTq){super(referent,q);}}
可以看到虚引用的get方法永远返回null,我们看个demo。publicstaticvoiddemo()throwsInterruptedException{ObjectobjnewObject();ReferenceQueueObjectrefQueuenewReferenceQueue();PhantomReferenceObjectphanRefnewPhantomReference(obj,refQueue);ObjectobjgphanRef。get();这里拿到的是nullSystem。out。println(objg);让obj变成垃圾objnull;System。gc();Thread。sleep(3000);gc后会将phanRef加入到refQueue中Referencelt;?extendsObjectphanRefPrefQueue。remove();这里输出trueSystem。out。println(phanRefPphanRef);}
从以上代码中可以看到,虚引用能够在指向对象不可达时得到一个通知(其实所有继承References的类都有这个功能),需要注意的是GC完成后,phanRef。referent依然指向之前创建Object,也就是说Object对象一直没被回收!
而造成这一现象的原因在上一小节末尾已经说了:对于Finalreferences和Phantomreferences,clearreferent字段传入的时false,也就意味着被这两种引用类型引用的对象,如果没有其他额外处理,在GC中是不会被回收的。
对于虚引用来说,从refQueue。remove();得到引用对象后,可以调用clear方法强行解除引用和对象之间的关系,使得对象下次可以GC时可以被回收掉。End
针对文章开头提出的几个问题,看完分析,我们已经能给出回答:
1。我们经常在网上看到软引用的介绍是:在内存不足的时候才会回收,那内存不足是怎么定义的?为什么才叫内存不足?
软引用会在内存不足时被回收,内存不足的定义和该引用对象get的时间以及当前堆可用内存大小都有关系,计算公式在上文中也已经给出。
2。网上对于虚引用的介绍是:形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。主要用来跟踪对象被垃圾回收器回收的活动。真的是这样吗?
严格的说,虚引用是会影响对象生命周期的,如果不做任何处理,只要虚引用不被回收,那其引用的对象永远不会被回收。所以一般来说,从ReferenceQueue中获得PhantomReference对象后,如果PhantomReference对象不会被回收的话(比如被其他GCROOT可达的对象引用),需要调用clear方法解除PhantomReference和其引用对象的引用关系。
3。虚引用在Jdk中有哪些场景下用到了呢?
DirectByteBuffer中是用虚引用的子类Cleaner。java来实现堆外内存回收的,后续会写篇文章来说说堆外内存的里里外外。
资料获取方式:关注小编转发文章私信【13】免费获取
重要的事情说三遍,转发转发转发,一定要记得点赞转发哦!!!
切费林看热闹不嫌事大,明知道梅西被诋毁,还在这里添油加醋关于梅西回归巴萨的话题层出不穷,但是目前拉波尔塔还没有任何关于梅西的报价,这似乎令人有些着急,因为当初放走梅西,根本不是所谓的工资帽问题,而是有意为之,所以,当梅西想要回归巴萨……
沙特主权财富基金收购英超纽卡斯尔联俱乐部新华社利雅得10月7日电(记者胡冠、王海洲)据沙特通讯社报道,沙特主权财富基金公共投资基金7日晚宣布,该基金与PCP资本、鲁本兄弟体育传媒投资集团已完成对英国纽卡斯尔联足球俱乐……
2022年五金材料采购合同范文2022年五金材料采购合同范文(精选20篇)2022年五金材料采购合同范文篇1采购方:(以下简称甲方)供应方:(以下简称乙方)第一条、标的、数量、价款及交(提……
2021安全生产会议纪要20xx安全生产会议纪要【一】4月28日下午,蓝山县安委会召集全县乡镇主要负责人,县安委会成员单位负责人,各乡镇安监站长、企业代表等近200人在县党校二楼召开第一季度安全……
职场中的常见注意事项与礼仪技巧职场礼仪影响着我们生活的方方面面,那么职场礼仪的常见注意事项与技巧有哪些呢?下面品学网小编为大家整理了职场礼仪的常见注意事项与技巧,希望大家能够喜欢。职场礼仪的常见注意事项与技……
2021年9月份党员学习内容20xx年9月份通过党员学习内容,深入开展以为民务实清廉为主要内容的党的群众路线教育实践活动是作出的一项重大部署,是继承优良传统、永葆党的先进性和纯洁性的必然要求。下面是品学网……
设备维修工作计划例文2021【篇一】一、201x年工作重点201x年工作重点是:保证设备良好运行,提高设备完好率,提升设备维修人员素质,全面加强设备维护、保养工作,避免因机械设备疏于保养出现故……
安全警示标志管理规定范文3篇安全警示标志管理规定的制定,旨在减少和防止重大安全事故的发生,警示操作人员,正确识别危险源,下面是安全警示标志管理规定范文,欢迎参阅。安全警示标志管理规定范文1为了确保施……
水果店创业项目策划书范文当今社会创业可以说是每个人心里怀揣的梦想。但是怎么创业如何创业是个很让人伤脑筋的事情。首先要选择好要创业的项目,其次就是创业的三大要素;现在,就来看看以下三篇关于水果店创业项目……
高铁实习心得体会总结3篇快速的高铁建设大背景下,城市化发展逐渐从单体城市扩张转向城市群的过程中,依托高铁站点落地的契机,国内出现高铁新城、高铁经济发展示范区等高铁站点地区的建设热潮。下面是品学网为大家……
高校班主任心得体会3篇高校班主任是高校教师队伍中集教学、教育和管理于一身的特殊群体。班主任自我成长与学生成长相辅相成,密不可分。下面是品学网带来的高校班主任的心得体会,欢迎欣赏阅读。高校班主任……
大学关于2021五一放假通知大学关于20xx五一放假通知各单位:根据国务院办公厅相关文件精神,现将我校20xx年五一劳动节放假安排通知如下:一、5月1日至3日放假调休,共3天。5月4日(……