深入分析OpenJDKG1FullGC原理
本文主要从代码层面介绍OpenJDKG1(garbagefirst)FullGC的工作原理,基于OpenJDK从GC(garbagecollection)入口处开始分析整个FullGC原理的核心代码与执行过程;并对比OpenJDK8与OpenJDK11中单线程与多线程实现FullGC的异同。希望帮助大家更深入地理解G1FullGC原理,并了解代码中可能导致GC时间长的对象分配方式。G1GC的知识回顾
为了帮助大家更全面地理解FullGC的逻辑和前因后果,我们先整体简单介绍下G1GC。GC主要指在程序运行过程中,对象不再使用后,由JVM提供的程序将对象销毁并回收内存,将该内存再加入到可用内存中;而G1GC,是JVM提供的一种垃圾回收器,可以通过多worker并行回收,并且通过CMS并发标记,此外通过region的分代回收,可以较好的控制单次回收的暂停时间,并且有效的抑制碎片化的问题。但G1并不是完美的回收器,相比于CMSGC,G1GCCPU负载相对较高;吞吐量在小内存上可能相对较弱,但G1GC整体在回收时间和效率上都表现较为优秀,是当前java业务中使用较为频繁的垃圾回收器。但随着使用场景的变换,为了在业务中更好地完成垃圾回收调优,可以更多地了解下这项技术的基础知识。G1将整个JVM运行时堆分为若干个region,region大小在运行之初设定,且一经设定不可改变;每个region都可能被作以下分别:包括Eden,Survivor,Old,Humongous和Free等区域,其中Edenregion指年轻代,一般对象(大小小于region的一半)对象分配的默认选择区域,并且为每次内存空间不足时的优先回收区;从一次垃圾回收存活下来的对象会被copy到survivorregion,经过几轮回收后,依然存活的对象太多,则这些对象会被移动到Oldregion;当对象大小大于region一半会被直接分配到Humongousregion,只有在FullGC或者CMS后才有机会回收。回收包含4部分YoungGC(Pause)、MixGC、ConcurrentMarking和FullGCYoungGC:选择全部年轻代(Eden),根据暂停时间要求决定回收年轻代的数量。stoptheworld(stw)暂停所有应用线程,G1创建回收集CollectionSet(cset)扫描root,从栈、classloader、JNIHandle等出发,扫描标记直接引用的对象基于要回收的region的rset来扫描标记对应region的存活对象然后将活对象copy到新region处理其他引用ConcurrentMarkingCycle:JVM内存使用比例超过一定阈值,会触发全局并发标记,包括cleanup阶段(这里会清理出部分大对象)MixGC:选择全部年轻代和部分老年代,会按照垃圾占比高低来排序,优先回收垃圾多的set,G1也得名于此(Garbagefirst),回收过程同YoungGC一样。FullGC:MixGC来不及回收或回收不掉,分配内存仍失败,就会触发全局回收,包括所有年轻代、老年代,大对象以及少量其他的JVM管理的内存,过程总体同YoungGC一样。不过扫描root过程会将整个堆完全扫描完。对于有些高流量的业务程序而言,适当的FullGC可以更好的调整整个内存,但一般情况而言,因为回收暂停时间较长,我们应当尽量避免FullGC。G1FullGC回收流程图简图如下
G1FullGC入口(触发条件)
触发FullGC的条件主要分两大类,第一类是在对象分配时发现内存不足导致分配失败,会触发回收逻辑,在younggcmixgc尝试回收后,都不能释放足够内存时,便会触发FullGC;第二类为业务代码中主动调用回收内存函数,比如System。gc。两类最终都会调用到docollection(),此处不涉及parallel,OpenJDK8和OpenJDK11入口相同各种分配失败时的调用入口G1CollectedHeap::memallocateVMG1CollectForAllocation::doit()G1CollectedHeap::satisfyfailedallocation()G1CollectedHeap::docollection()System。gc()和metadata回收G1CollectedHeap::collect(GCCause::Causecause)VMG1CollectFull::doit()G1CollectedHeap::dofullcollection()G1CollectedHeap::docollection()复制代码G1FullGC回收前准备preparecollection
从docollection调入后,对于OpenJDK8,以G1MarkSweep::invokeatsafepoint函数为边界,该函数前为回收准备,之后为收尾工作;而OpenJDK11则将整个preparecollection、collect和complete三步都用g1FullCollector类进行封装,逻辑更加清晰。
准备过程两者相似,可简单分为三类数据信息检查及记录:包括计时器,trace、safepoint检查、日志打印等数据处理部分concurrentmark相关操作和暂停:包括concurrentmark的abort,cmdiscovery禁用等回收准备:heap集合整理清除、policy策略更新、开启stwdiscovery、处理对象偏向锁等openjdk11voidG1FullCollector::preparecollection(){heapg1policy()recordfullcollectionstart();heapprintheapbeforegc();heapprintheapregions();终止并发标记,禁用cmdiscoveryheapabortconcurrentcycle();heapverifybeforefullcollection(scope()isexplicitgc());预处理当前堆heapgcprologue(true);heapprepareheapforfullcollection();。。。保存偏向锁的markword并进行初始标记BiasedLocking::preservemarks();clearandactivatederivedpointers();}voidBiasedLocking::preservemarks(){。。。ThreadcurThread::current();for(JavaThreadthreadThreads::first();thread!NULL;threadthreadnext()){if(threadhaslastJavaframe()){RegisterMaprm(thread);for(javaVFramevfthreadlastjavavframe(rm);vf!NULL;vfvfjavasender()){GrowableArrayMonitorInfomonitorsvfmonitors();if(monitors!NULL){intlenmonitorslength();Walkmonitorsyoungesttooldestfor(intilen1;i0;i){MonitorInfomoninfomonitorsat(i);if(moninfoownerisscalarreplaced())continue;oopownermoninfoowner();if(owner!NULL){markOopmarkownermark();if(markhasbiaspattern()){preservedoopstackpush(Handle(cur,owner));preservedmarkstackpush(mark);}}}}}}}}复制代码
上述代码为垃圾回收前准备过程的封装,OpenJDK8和OpenJDK11整体过程基本一致(可进入调用函数与OpenJDK8对照,此处不展开),区别在于OpenJDK11会在g1FullCollector初始化过程中,根据parallel的使用情况对preservestack、oopstack、arrayOopstack、compactionpoints等数据结构按照并行的线程数进行切分,初始化并用数组保存,为parallel回收做准备,服务于并行FullGC。openjdk11G1FullCollector::G1FullCollector(G1CollectedHeapheap,GCMemoryManagermemorymanager,boolexplicitgc,boolclearsoftrefs):heap(heap),scope(memorymanager,explicitgc,clearsoftrefs),numworkers(calcactiveworkers()),oopqueueset(numworkers),arrayqueueset(numworkers),preservedmarksset(true),serialcompactionpoint(),isalive(heapconcurrentmark()nextMarkBitMap()),isalivemutator(heaprefprocessorstw(),isalive),alwayssubjecttodiscovery(){assert(SafepointSynchronize::isatsafepoint(),mustbeatasafepoint);preservedmarksset。init(numworkers);markersNEWCHEAPARRAY(G1FullGCMarker,numworkers,mtGC);compactionpointsNEWCHEAPARRAY(G1FullGCCompactionPoint,numworkers,mtGC);for(uinti0;inumworkers;i){markers〔i〕newG1FullGCMarker(i,preservedmarksset。get(i),markbitmap());compactionpoints〔i〕newG1FullGCCompactionPoint();oopqueueset。registerqueue(i,marker(i)oopstack());arrayqueueset。registerqueue(i,marker(i)objarraystack());}}复制代码G1FullGC回收核心逻辑collect
回收核心过程基本分为4步如下,包括标记阶段(phase1markliveobjects),复制和回收准备阶段(phase2preparecompaction),调整指针阶段(phase3adjustpointers)和压缩清理阶段(phase4docompaction):openjdk11voidG1FullCollector::collect(){phase1markliveobjects();phase2preparecompaction();phase3adjustpointers();phase4docompaction();}复制代码
OpenJDK11中,这4步骤被封装成4个task,继承自AbstractGangTask,都保存有monitor和可执行的GangWorker,利用runtask来根据worker数目分多线程执行task,而执行逻辑封装在task的work函数中,每个task中会传入workerid,操作时根据workerid来利用G1FullCollector的数据结构来进行操作。voidG1FullCollector::phase1markliveobjects(){GCTraceTimetm(Phase1:Markliveobjects,G1Log::fine(),true,scope()timer(),scope()tracer()gcid());Dotheactualmarking。G1FullGCMarkTaskmarkingtask(this);runtask(markingtask);。。。}voidG1FullCollector::runtask(AbstractGangTasktask){。。。heapworkers()runtask(task);。。。}复制代码1。phase1markliveobjects
以marker过程为例,第5行根据workerid取到collector中准备好的数据结构,通过rootprocessor来来执行三个闭包函数,最后会一路调用到每个OOP的markandpush,对整个堆进行标记,此过程与OpenJDK8基本一致。openjdk11voidG1FullGCMarkTask::work(uintworkerid){TicksstartTicks::now();ResourceMarkrm;G1FullGCMarkermarkercollector()marker(workerid);MarkingCodeBlobClosurecodeclosure(markermarkclosure(),!CodeBlobToOopClosure::FixRelocations);if(ClassUnloading){rootprocessor。processstrongroots(markermarkclosure(),markercldclosure(),codeclosure);}else{rootprocessor。processallrootsnostringtable(markermarkclosure(),markercldclosure(),codeclosure);}Markstackispopulated,nowprocessanddrainit。markercompletemarking(collector()oopqueueset(),collector()arrayqueueset(),terminator);Thisisthepointwheretheentiremarkingshouldhavecompleted。assert(markeroopstack()isempty(),Markingshouldhavecompleted);assert(markerobjarraystack()isempty(),Arraymarkingshouldhavecompleted);}voidG1RootProcessor::processstrongroots(OopClosureoops,CLDClosureclds,CodeBlobClosureblobs){processjavaroots(oops,clds,clds,NULL,blobs,NULL,0);processvmroots(oops,NULL,NULL,0);processstrongtasks。alltaskscompleted();}voidG1RootProcessor::processvmroots(OopClosurestrongroots,OopClosureweakroots,G1GCPhaseTimesphasetimes,uintworkeri){{G1GCParPhaseTimesTrackerx(phasetimes,G1GCPhaseTimes::UniverseRoots,workeri);if(!processstrongtasks。istaskclaimed(G1RPPSUniverseoopsdo)){Universe::oopsdo(strongroots);}}各类可能的roots。。。}boolSubTasksDone::istaskclaimed(uintt){assert(0ttntasks,badtaskid。);uintoldtasks〔t〕;if(old0){oldAtomic::cmpxchg(1,tasks〔t〕,0);}boolresold!0;。。。returnres;}templateclassTinlinevoidG1FullGCMarker::markandpush(Tp){TheapoopoopDesc::loadheapoop(p);if(!oopDesc::isnull(heapoop)){oopobjoopDesc::decodeheapoopnotnull(heapoop);if(markobject(obj)){oopstack。push(obj);assert(bitmapisMarked((HeapWord)obj),Mustbemarkednowmapself);}else{assert(bitmapisMarked((HeapWord)obj),Mustbemarkedbyother);}}}复制代码
补充多线程实现思路
以mark过程为例,简单说明下多线程回收思路。
1)首先在java程序执行时,可以通过添加XX:ParallelGCThreads8指定具体回收时的线程数;在启动后,SharedHeap对象的初始化时,会调用WorkGang::initializeworkers(),初始化8个GC线程(一般是以GangWorker实现,继承自WorkerThread,也被称为工人线程)。随后线程开始执行会调用到GangWorker的run函数,调到loop,进入wait阶段等待唤醒来执行任务,这个循环等待会持续整个java程序运行过程。voidGangWorker::run(){initialize();loop();}voidGangWorker::loop(){。。。Monitorgangmonitorgang()monitor();for(;!terminate();){WorkDatadata;intpart;Initializedbelow。{MutexLockerml(gangmonitor);gang()internalworkerpoll(data);for(;breakorreturn;){Checkfornewwork。if((data。task()!NULL)(data。sequencenumber()!previoussequencenumber)){if(gang()needsmoreworkers()){gang()internalnotestart();gangmonitornotifyall();partgang()startedworkers()1;break;}}Nothingtodo。gangmonitorwait(nosafepointchecktrue);gang()internalworkerpoll(data);。。。data。task()work(part);。。。}}}}复制代码
2)所有的XXXTask继承自AbstractGangTask,会实现work方法;在回收过程执行时,会将待执行的task传给heap的workers来执行,即会调用到Sharedheap中提前保存好的FlexibleWorkGang的runtask。而FlexibleWorkGang继承自WorkGang,runtask方法实现如下,会唤醒所有GC线程,并等待所有线程执行结束后返回。workgroup。cppvoidFlexibleWorkGang::runtask(AbstractGangTasktask){WorkGang::runtask(task,(uint)activeworkers());}voidWorkGang::runtask(AbstractGangTasktask,uintnoofparallelworkers){tasksetfortermination(noofparallelworkers);MutexLockerExml(monitor(),Mutex::nosafepointcheckflag);。。。monitor()notifyall();Waitforthemtobefinishedwhile(finishedworkers()noofparallelworkers){if(TraceWorkGang){ttyprintcr(Waitinginworkgangs:ddfinishedsequenced,name(),finishedworkers(),noofparallelworkers,sequencenumber);}monitor()wait(nosafepointchecktrue);}。。。}复制代码
3)task开始执行后,会通过processor执行器执行到每个task的work方法,并根据执行线程传入workerid,比如G1FullGCMarkTask的rootprocessor。processstrongroots函数,调用关系的其中一条分支如下,每一个workgang都会通过ALLJAVATHREADS来顺序遍历所有的java线程来执行mark操作,若已经执行过或正在执行就跳过,保证执行完所有线程,包括一个VMThread,其中mark过程传入的OopClosure、CLDClosure为对应workerid的markclosure、cldclosure,执行时会调用每个marker准备好的oopStack等数据结构来执行,都执行结束后所有的GangWorker继续进入等待任务的过程中,直到下一个runtask。processstrongrootsprocessjavarootsThreads::possiblyparalleloopsdovoidThreads::possiblyparalleloopsdo(OopClosuref,CLDClosurecldf,CodeBlobClosurecf){SharedHeapshSharedHeap::heap();boolisparshnparthreads()0;assert(!ispar(SharedHeap::heap()nparthreads()SharedHeap::heap()workers()activeworkers()),Mismatch);intcpSharedHeap::heap()strongrootsparity();ALLJAVATHREADS(p){if(pclaimoopsdo(ispar,cp)){poopsdo(f,cldf,cf);}}VMThreadvmtVMThread::vmthread();if(vmtclaimoopsdo(ispar,cp)){vmtoopsdo(f,cldf,cf);}}复制代码2。phase2preparecompaction
标记过后,准备压缩清理,根据标记情况计算地址,详细逻辑在G1CalculatePointersClosure实现如下;根据标记结果,通过doHeapRegion遍历含有存活对象的region,并将存活对象复制到新的region内,copy同时将对象头指向旧对象,为调整指针和清理旧region做准备。openjdk11voidG1FullGCPrepareTask::work(uintworkerid){。。。G1FullGCCompactionPointcompactionpointcollector()compactionpoint(workerid);G1CalculatePointersClosureclosure(collector()markbitmap(),compactionpoint);G1CollectedHeap::heap()heapregionpariteratefromstart(closure,hrclaimer);。。。}voidG1CollectedHeap::heapregionpariteratefromstart(HeapRegionClosurecl,HeapRegionClaimerhrclaimer)const{hrm。pariterate(cl,hrclaimer,0);}voidHeapRegionManager::pariterate(HeapRegionClosureblk,HeapRegionClaimerhrclaimer,constuintstartindex)const{constuintnregionshrclaimernregions();for(uintcount0;countnregions;count){constuintindex(startindexcount)nregions;。。。HeapRegionrregions。getbyindex(index);if(hrclaimerisregionclaimed(index)){continue;}if(!hrclaimerclaimregion(index)){continue;}boolresblkdoheapregion(r);if(res){return;}}}boolG1FullGCPrepareTask::G1CalculatePointersClosure::doHeapRegion(HeapRegionhr){if(hrisHumongous()){oopobjoop(hrhumongousstartregion()bottom());if(bitmapisMarked((HeapWord)obj)){if(hrstartsHumongous()){objforwardto(obj);}}else{freehumongousregion(hr);}}elseif(!hrisHumongous()){prepareforcompaction(hr);}resetregionmetadata(hr);returnfalse;}复制代码3。phase3adjustpointers
此步骤和phase2类似,遍历存活对象region,通过对象头记录的地址,将live的对象引用指向重新计算的地址。由phase2和phase3两个步骤可见,同样大小的内存占用,在GC过程中,存活对象数量越多会引起更多的复制和调整指针工作,导致更长的总回收时间。openjdk11voidG1FullGCAdjustTask::work(uintworkerid){。。。Needstobelast,processallrootscallsalltaskscompleted(。。。)。rootprocessor。processallroots(adjust,adjustcld,adjustcode);。。。在此步骤前adjust掉weak,preservedstring等等,此步骤adjustpointersregionbyregionG1AdjustRegionClosureblk(collector()markbitmap(),workerid);G1CollectedHeap::heap()heapregionpariteratefromworkeroffset(blk,hrclaimer,workerid);logtask(Adjusttask,workerid,start);}复制代码4。phase4docompaction
最后逐个region进行处理,若没有存活对象,清理掉region;若有存活对象,则将对象按之前计算copy到新地址,将旧region重置。openjdk11voidG1FullGCCompactTask::work(uintworkerid){TicksstartTicks::now();GrowableArrayHeapRegioncompactionqueuecollector()compactionpoint(workerid)regions();for(GrowableArrayIteratorHeapRegionitcompactionqueuebegin();it!compactionqueueend();it){compactregion(it);}G1ResetHumongousClosurehc(collector()markbitmap());G1CollectedHeap::heap()heapregionpariteratefromworkeroffset(hc,claimer,workerid);。。。}voidG1FullGCCompactTask::compactregion(HeapRegionhr){。。。G1CompactRegionClosurecompact(collector()markbitmap());hrapplytomarkedobjects(collector()markbitmap(),compact);if(!hrisempty()){MemRegionmr(hrbottom(),hrtop());collector()markbitmap()clearRange(mr);}hrcompletecompaction();}sizetG1FullGCCompactTask::G1CompactRegionClosure::apply(oopobj){sizetsizeobjsize();HeapWorddestination(HeapWord)objforwardee();if(destinationNULL){returnsize;}HeapWordobjaddr(HeapWord)obj;assert(objaddr!destination,everythinginthispassshouldbemoving);Copy::alignedconjointwords(objaddr,destination,size);oop(destination)initmark();assert(oop(destination)klass()!NULL,shouldhaveaclass);returnsize;}inlinevoidHeapRegion::completecompaction(){。。。if(usedregion()。isempty()){resetbot();}。。。if(ZapUnusedHeapArea){SpaceMangler::mangleregion(MemRegion(top(),end()));}}复制代码G1FullGC回收收尾complete
在整个回收结束前,需要进行一些收尾工作,此处并无parallel区分。在prepareheapformutators中,会包括重建region集合,将空的region加入freelist;为每个region重建strongroot列表;删除已卸载类加载器的元空间,并清理loaderdata图;准备collectionset;重新处理卡表信息以及一些验证和信息打印等。openjdk11voidG1FullCollector::completecollection(){restoremarks();BiasedLocking::restoremarks();CodeCache::gcepilogue();JvmtiExport::gcepilogue();heapprepareheapformutators();。。。}复制代码总结
相比于YoungGC和ConcurrentMarkingCycle等,G1回收器中FullGC过程更为独立、完整,但涉及面也更广,涉及到JVM对整个堆的管理细节,包括OOP对象结构,region块,bitmap标记管理,起始root管理,甚至GC线程后台运行情况,多线程同步,stw机制等等,每一部分的设计都很巧妙,值得我们深入代码中一探究竟,相信大家一定会有所收获。
五门轿跑型格HATCHBACK上市,售13。99万元起2月28日,广汽本田INTEGRA型格HATCHBACK上市发布会暨INTEGRA型格潮牌全球首发会在广州举办。五门轿跑型格HATCHBACK掀潮上市,新车共推出6个版本,MT手动
5年前,那个走4。5公里山路上学,满头冰花的男孩,如今怎样了?2018年1月,一张由老师无意间拍下的照片突然在网络上走红,引起了社会大众的关注。照片中的男孩小小年纪却早生华发,仔细看来竟是一头的冰渣,那稚嫩的小脸也被冻的发红。身后的同学看着小
勇担金融使命高质量服务地方经济发展于金宝日前,在江苏海安市委市政府召开的2022年度总结表彰大会上,海安农商银行喜获服务地方先进单位纳税贡献超亿元企业实施乡村振兴战略先进单位服务地方经济发展优胜金融单位四项殊荣。一
抢不到票?!带你直接走后门是谁还没抢到演唱会门票?!是你是我是大家o()o没关系!30位娱乐圈老公等你来翻牌子翻到最后更有百分百中奖的好礼!身为女生这么幸福的事千万别错过!除了可以体验翻牌顶流男神!女神到店
烟台总动员决胜万亿GDP解题工业新旧动能转换编者按万亿GDP对城市意味着什么?2023年,烟台与常州成为国内两个最有望突破万亿GDP的城市,在全国拼经济的大环境下,如何突围?2月末,21世纪经济报道记者在山东烟台市莱山区的一
张学良晚年坦言杀杨宇霆之前我从不迷信,杀他后我不得不信1929年1月,隆冬,纷纷扬扬的大雪将沈阳城装扮成了一片银装素裹的冰城。正当人们还沉浸在过节的气氛时,一声清脆的枪声响彻沈阳城的上空。紧接着,两个卫兵就急匆匆的运着两个血淋淋的麻袋
免费医疗不如早点退休四川大学教授甘华田准备提出全面免费医疗提案,他将建议把全民免费医疗列为基本国策,并尽快逐步推进6岁以下儿童80岁以上老年人免费医疗。看得出来,甘华田教授的提案很接地气,但我个人认为
两会将至,看这些人大代表,政协议员的提议,有没有戳中你的心?都说每年的三月份,全国都非常热闹,因为又到了一年一度全国大会,除了好奇领导人和干部的选举,还有一个很重要的原因,那就是议员们为老百姓的提议。今年也不例外,大会还没有开启之前,很多政
宅男财经吉利是否抄袭长安?听听律师怎么说视频加载中近日,一份网传律师函指出,吉利银河之光原型车涉嫌抄袭长安汽车,而吉利汽车方面发布声明否认。北京市朝阳区律师协会知产委员会委员北京声驰律师事务所张杨律师表示,由于现有信息有
聚焦智慧民航推动飞机智慧维修打造数字MRO企业党的二十大报告指出,加快发展数字经济,促进数字经济和实体经济深度融合。当前,数字化信息化已经成为航空维修企业高质量发展的引擎。近年来,北京飞机维修工程有限公司(以下简称Ameco)
海博出租驾驶员学雷锋,免费送康复病患出院回家今天上午,华山医院住院部门口人流熙熙攘攘,办理好出院手续的康复病人在家属的陪同下,提着各种行李物品正陆续走出医院。他们惊喜地看到,一辆辆崭新的海博出租车正等候在门口,驾驶员们热情地