当前位置: 首页 > 后端技术 > Java

破碎的!面试官问我关于垃圾回收机制

时间:2023-04-01 13:37:44 Java

面试官:我还记得你上次提到JVM内存结构(运行时数据区)的时候提到了“堆”,然后你说分为几个区域.面试官:当时觉得再说下去可能要加班了。面试官:今天有点闲,我们继续说“堆”考生:嗯,前面说了,堆分为“新生代”和“老年代”,“新生代分为”伊甸园”和“Survivor”区域,而“Survivor”区域又分为“FromSurvivor”和“ToSurvivor”区域面试官:那开始你的表演吧。考生:我们在用Java的时候,创建了很多对象,但是从来没有“manually”清除了这些对象,是一个需要自己释放(release)的候选:为什么我们写Java的时候不自己手动释放“garbage”呢?原因很简单,JVM帮我们做了(automatic垃圾回收)面试官:嗯...考生:我个人对垃圾的定义:只要对象不再被使用,那么我们就认为这个对象是垃圾,对象占用的空间是可以回收的面试官:那如何判断对象不再被使用呢?考生:常用的算法有两种,“引用计数法”和“可达性分析法”。当计数器为0时,表示该对象不再被引用,可以回收了。B取决于A)面试官:嗯……应聘者:另一种是可达性分析法:从“GCRoots”开始往下找。当对象与“GCRoots”之间没有引用链接时,表示该对象不可用,可以被回收候选者:“GCRoots”是一组必须是“活跃”的引用。从“GCRoot”开始,程序可以通过直接或间接引用找到可能正在使用的对象。面试官:我还是不明白,“GCRoots”一般指的是什么?你说是一组activereferences,能举个例子吗,太抽象了。考生:比如上次我们不是讲了JVM内存结构中的虚拟机栈吗?虚拟机栈中不是有栈帧吗?栈帧不是有局部变量吗?不要局部变量只存储引用。考生:如果栈帧在虚拟机栈顶,是否说明栈帧处于活动状态(也就是说线程正在被调用)考生:既然线程在调用,那么栈帧就是对象指向“堆”的引用一定是“活动”引用?考生:所以,指向堆中对象引用的当前活跃栈帧可以是“GCRoots”面试官:嗯……考生:当然,上面那一小块考生并不是唯一可以作为“GCRoots”考生:比如一个类的静态变量引用是“GCRoots”,“JavaNativeMethod”引用的对象也是“GCRoots”等等……考生:回到正题理解:“GCRoots”是一组“Active”引用,只要没有对“GCRoots”的直接或间接引用,它们就是垃圾候选者:JVM使用“AccessibilityAnalysisAlgorithm”来判断是否objectisgarbage面试官:知道了候选人:垃圾回收的第一步是“标记”,标记没有被“GCRoots”引用的对象候选者:标记之后,我们可以直接选择“清除”,只要它们与“GCRoots”没有关联淘汰候选人:过程非常简单粗暴,但存在明显的问题。考生:直接清除会造成“内存碎片”问题:可能我有10M空闲内存,但是程序申请不到9M内存空间(10M内存空间在垃圾清理后不连续)考生:解决“内存碎片”问题也比较简单粗暴。“标记”后,不要直接“清除”。考生:我把“标记”好的存活对象“复制”到另一个空间。复制之后,我直接杀掉原来的整个空间!这样就不存在内存碎片的问题了。考生:这种做法缺点很明显:内存利用率低,还得给我复制(移动)一个新的区域。面试官:嗯……考生:还有一个“折叠”我不需要有一个“大完整空间”来解决内存碎片的问题,我只需要能够在“当前区域”移动考生即可》:把存活的对象移到一边,把垃圾移到一边,然后把垃圾一起删除,不就没有内存碎片了吗?考生:这个专业术语叫“组织”:经过研究表明,大多数对象的生命周期都很短,只有少数对象可能会长期存活:理解“停止”应该很简单世界”:在回收垃圾时,程序短时间内无法继续正常运行。不然JVM在回收的时候,用户线程会继续分配和修改引用,JVM会怎么样呢(:候选人:为了让“stoptheworld”的持续时间尽可能短,改善内存分配ratethatconcurrentGCcanhandleCandidate:在很多垃圾收集器上,这两类对象是从“物理”或“逻辑”上区分的,被快速消亡的对象所占据的区域称为“年轻代”,所占的区域byobjectsthatlivelong区域被称为“oldgeneration”candidates:但不是所有的“garbagecollectors”都会有,但是我们现在线上可能都在用JDK8,JDK8及以下使用的垃圾收集器都有“partitionGeneration”考生:所以,大家可以看到我的“堆”是用“年轻代”和“老年代”画出来的考生:值得注意的是,ZGC没有代的概念(:考生:我只是想说明现状,如果ZGC是免费的,我们再说吧。面试官:嗯……好吧考生:前面提到了垃圾回收的过程,其实对应的是几种“垃圾回收算法”,它们是:考生:标记去除算法、标记复制算法和标记排序算法["标记","clear","copy"and"organize"]考生:经过上面的铺垫,这些算法应该比较容易理解考生:“分代”和“垃圾收集算法”理解之后,我们可以看一下JDK8及以下生产环境中常见的垃圾收集器。候选:“年轻代”的垃圾收集器包括:Seria、ParallelScavenge、ParNew候选:“老年代”的垃圾收集器包括:SerialOld、ParallelOld、CMSUnderstood。Serial是单线程的,Parallel多线程候选者:这些垃圾收集器实际上是“实现”了垃圾收集算法(标记复制、标记整理、标记清除算法)候选者:CMS是“JDK8之前”的一种比较新的垃圾收集器,其特点是能够尽可能减少“停止世界”的时间。允许用户线程和GC线程在垃圾回收时并发执行!候选:还可以发现“年轻代”垃圾回收器使用的是“标记复制算法”候选:所以在“堆内存”划分中,将年轻代划分为Survivor区(SurvivorFrom和SurvivorTo),目的是有一个完整的内存空间供垃圾收集器复制(移动)候选:并且新的对象被放置在伊甸区默认比例的候选:我已经画好了,不用解释了再次。面试官:我还是想问一下,就是新创建的对象一般都在“新生代”。什么时候到“晚年”?考生:嗯,我觉得简单分两种情况:考生:1.如果对象太大,会直接进入老年代(创建时对象很大||幸存区不能存放对象)候选:2.如果对象太老,则提升到老年代(每次MinorGC发生,存活对象的年龄+1,默认值15提升到老年代||动态判断对象年龄可以进入老年代)面试官:既然你又提到了MinorGC,那么什么时候会触发MinorGC?Candidate:当Eden区空间不足时会触发MinorGC。面试官:MinorGC就是我理解中的“年轻代”GC。您之前提到过“GCRoots”。面试官:那么在“新生代”“GC的时候,从GCRoots开始,那不也扫描一下”老年代“的对象吗?那那个……不就相当于全堆扫描了吗?考生:这个JVM也有一个解决方案考生:HotSpot虚拟机的“旧GC”(G1以下)要求整个GC堆在一个连续的地址空间考生:所以会有一个分界线(一边是老年代,另一边是新生代),所以可以通过“地址”来判断对象在哪一代Candidates:做MinorGC的时候,从GC开始,从Roots开始,如果在“老年代”,那就不要往下了(MinorGC对老年代区域不感兴趣)面试官:但是还有一个问题,如果“年轻代”里面的对象是“老年代”参考?(老年代对象持有新生代对象的引用),那么“新生代”中的对象此时一定不能被回收。候选:HotSpot虚拟机下有一个“卡表”(cardtable),避免全局扫描“老年代”对象表格实际上是一张卡片页面的集合。当判断某个卡片页存在对已有对象的跨代引用时,将该页标记为“脏页”候选:知道了“卡片表”后,就好办了。每次MinorGC,只需要去“卡表”中寻找“脏页”,找到后加入GCRoot,而不用遍历整个“老年代”对象。面试官:嗯,没关系,你为什么不继续说CMS呢?应聘者:这次面试差不多一个小时,画了那么多图。下次?下次?有点累这篇文章总结一下:什么是垃圾:只要不再使用的对象就是垃圾如何判断为垃圾:可达性分析算法和参考计算算法,JVM使用可达性分析算法什么是GCRoots:GCRoots是一组必须处于活动状态的引用。与GCRoots无关的引用是垃圾,可以回收。常见的垃圾回收算法:标记清除、标记复制和标记整理为什么需要生成:大多数对象都死了在早期,只有一小部分对象存活了很长时间。堆内存会在物理上或逻辑上进行分代,以尽可能缩短“stoptheworld”的持续时间,并提高并发GC可以处理的内存分配率。MinorGC:Eden区满时触发,从GCRoots开始遍历,年轻代GC不关心老年代对象是什么。相应地,MinorGC也顺利进行(案例:老年代对象持有新生代对象的引用)。堆内存比例:新生代占堆内存的1/3,老年代占堆内存的2/3。Eden区占新生代的8/10,Survivor区占新生代的2/10(其中From和To各占1/10)欢迎关注我的微信公众号【Java3y】说说Java面试,在线面试官系列持续更新中!【在线面试官-手机版】系列,每周两篇,持续更新中!【在线面试官-电脑】系列每周两篇持续更新中!原创不易!!一连求三!!