当前位置: 首页 > 科技观察

面试官问为什么新生代不用mark-and-clear算法_0

时间:2023-03-19 17:03:30 科技观察

本文转载自微信公众号“Angela的博客”,作者Angela。转载本文请联系Angela博客公众号。杭州某写字楼内,安吉拉穿着新买的19.9皮鞋走进玻璃隔间办公室,准备迎接新的演出。面试官:你简历上好像对JVM比较熟悉吧?Angela:是的面试官:那说说堆内存的分区吧。Angela:[思考]:这个很简单,来,我们来复习一下JVM的基础知识。我们知道堆分为新生代和老年代。新生代就是我们所说的YongGeneration,老年代就是OldGeneration。采访者:然后呢?安吉拉:然后呢?采访者:结束了吗?新一代呢?安吉拉:如果你想听,可以告诉我。如果你不告诉我,我怎么知道你想要什么?听。新生代分为Eden区和Survivor区。Survivor由From区和To区组成。我给你画一个完整的内存结构。不要吸烟。把笔递给我,我来画,如下图。面试官:哦,图没问题,那堆为什么要分新生代和老年代呢?Angela:当然是为了更有效的内存管理。面试官:你怎么说?Angela:假设,如果不区分新老代,内存是一整块,垃圾收集器每次回收时都要把那些长寿命的对象和短生命周期的对象放在一起回收。通常,生命周期长的对象可能与应用程序的生命周期一致。你基本上不能回收它们。例如,Spring框架中Bean管理相关的对象(ApplicationContext)贯穿于应用程序的运行过程中。通常,这些对象会被回收多次。最后都放到了老年代,但是如果不区分新老代,每次都会一起回收,性能消耗很大。区分新老代后,老年代存放长寿命对象,新生代存放短寿命对象。老年代中的对象非常稳定,新生代的回收不影响老年代,回收效率可以大大提高。采访者:那为什么新生代分为Eden、From、To区呢?Angela:[变得有趣了]首先,大多数对象的生命周期都很短。如果新生代不分多个区域,新生代可能有两种回收方案。第一种可能:每次回收都是对新生代中的整块内存进行的。完整的垃圾回收过程分为三步:首先需要找到需要清理的对象标签;清理这些被标记的对象Object;移动剩余的对象,将达到老年代晋升年龄的对象移动到老年代。对象被回收后,会产生大量的内存碎片(回收的对象很多)。如果要解决内存碎片,需要移动剩余的对象(标记排序算法),整个回收过程效率很低。第二种可能:如果没有Survivor区(From+To),在MinorGC(新生代回收)过程中,存活的对象直接送到老年代,这样老年代会很快被填满,触发MajorGC(因为MajorGC一般都会伴随着MinorGC,也可以看成是触发了FullGC)。FullGC频繁影响程序的执行和响应速度。新生代的回收称为MinorGC,老年代的回收称为MajorGC。采访者:为什么要设置两个幸存者区?FromandToAngela:我们来看看,如果只有一个Survivor区,新生代的内存回收过程。根据上图,第一次Eden区满了,内存回收很简单,直接把Eden区存活的对象放到Survivor区;第二次内存回收需要回收两个地方,Eden区和Survivor区。因为Survivor区也会有需要回收的存活对象,所以Survivor区要使用标记排序垃圾回收算法(先标记需要清理的对象,然后回收,再把剩下的存活对象放在一起);Eden区使用复制算法,将Eden区存储的对象复制到Survivor区,然后清空整个Eden区。看到网上有文章说这里设置两个Survivor区的原因是为了避免内存碎片,因为他假设对于第二次(以后)回收,内存回收是先回收Eden区,再回收Survivor区,所以当然会有Memory碎片,但是如果真的只有一个Survivor区,垃圾回收设计者必须先回收Survivor区,再回收Eden区,等待Survivor区被回收排序,然后将Eden区存放的对象移动到Survivor区,这样生存地址是连续的,不会产生内存碎片。所以真正的原因是我下面提到的效率问题。面试官:这有什么问题吗?Angela:这个有几个问题:几次回收后,Survivor区满了怎么办?直接移动到老年代?老一代很快就会爆发。搬到伊甸区?发生内存碎片。可能Survivor区和Eden区回收后,需要重新组织内存,去除内存碎片,性能消耗也很大。一般来说,标记算法的性能消耗大于复制算法,尤其是在新生代中,98%的对象是“生死存亡”,98%的对象被明确标记,剩下的2%的对象,要整理内存,否则就把2%的对象放到别的地方,清空整个内存。Eden在清除整个内存方面非常高效。所以归根结底,这两个Survivor区还是出于性能的考虑,标记复制算法比标记排序效率更高。面试官:那详细说说除Eden之外的新生代标记,以及使用两种Survivor区标记复制算法。Angela:新生代中98%的对象都是“生死存亡”,所以没必要按照1:1的比例划分Eden和Survivor的空间,而是将新生代划分成更大的Edenspace和两个较小的Survivor空间,每次只使用Eden和Survivor[0](Fromarea)中的一个,留下Survivor[1](Toarea)进行标记复制。回收时,一次性将Eden和Survivor[0]中的存活对象复制到另一个Survivor[1](To)空间,最后清理掉刚刚使用过的Eden和Survivor空间。还有一点:From区和To区会在每次MinorGC后相互切换。From区域变为To区域,To区域变为From区域。这只是一个合乎逻辑的识别。HotSpot虚拟机默认Eden和Survivor的大小比例为8:1(CMS不适用),即每次新生代可用内存空间为整个新生代容量的90%(80%+10%),只有10%的内存会被“浪费”(总有10%的内存(SurvivorToarea)不存储任何东西)。标记复制算法流程:Eden区+SurvivorFrom区已满,标记存活对象,将存活对象复制到SurvivorTo区;SurvivorTo区域成为From区域(逻辑标识符),From区域成为To区域;内存分配,继续步骤1,如果复制过程中达到了老年代的提升年龄(默认值为15),则移动到老年代。面试官:我刚才说了那么多,你来之前把题目都背了吗?Angela:[思考]答不上来就说我不搞技术,能答上来就说我背题了,WTF。.耐心给面试官解释:怎么可能,我来之前把Angela博客的所有文章都看完了公众号,嘿嘿。面试官:看哪里,你分享给我。面试官:咦,老年代的内存回收策略呢?还有标记算法?另外说说几种常见的垃圾收集器,CMS和G1。安吉拉:我不想再谈了。我累了。不如我以后再说吧。面试官:没关系,我还是第二面试官,你说吧。安吉拉:我真的不想谈论它。面试官:那今天先过来,回去等通知,你从这个门出去左转。文章基于读者的问题。