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

面试官:MinorGC、MajorGC、FullGC有什么区别?我什至无法回答,.

时间:2023-04-02 09:53:38 Java

作者:javacodegeeks来源:http://www.importnew.com/1582...在Plumbr从事GC暂停检测相关功能时,我被迫通过大量文章、书籍和演讲介绍我的工作做。在整个过程中,经常会混淆Minor、Major和FullGC事件的使用。这就是我写这篇博客的原因,我希望能把一些困惑解释清楚。本文要求读者熟悉JVM内置的一般垃圾回收原则。堆内存分为Eden、Survivor和Tenured/Old空间,分代假设和其他不同的GC算法不在本文讨论范围之内。MinorGC从年轻代空间(包括Eden和Survivor区)回收内存,称为MinorGC。这个定义清晰易懂。然而,当MinorGC事件发生时,有一些有趣的事情需要注意:1.MinorGC是在JVM无法为新对象分配空间时触发的,比如Eden区已满。所以分配率越高,MinorGC执行的频率就越高。2.当内存池满了,会复制里面的所有内容,指针会从0开始跟踪空闲内存。Eden和Survivor区域有标记和复制操作,代替经典的mark,scan,compact,clean手术。所以Eden区和Survivor区不存在内存碎片。写指针始终位于已用内存池的顶部。3.在执行MinorGC操作时,永久代不会受到影响。从永久代到年轻代的引用被视为GC根,从年轻代到永久代的引用在标记阶段被忽略。4.质疑所有MinorGC都会触发“stop-the-world”停止应用线程的常规认知。对于大多数应用程序,暂停造成的延迟可以忽略不计。事实上,Eden区的大部分对象都可以被认为是垃圾,永远不会被复制到Survivor区或旧空间。反之,如果Eden区的大部分新对象都不满足GC条件,MinorGC执行时的停顿时间就会长很多。那么MinorGC的情况就很清楚了——每次MinorGC都会清理年轻代的内存。MajorGC与FullGC您应该注意,目前,这些术语在JVM规范或垃圾收集研究论文中都没有正式定义。但是我们一眼就可以看出,这些基于我们已知的定义的定义是正确的。MinorGC应该是为了清理年轻内存而设计的:MajorGC是为了清理老年代内存。FullGC就是清理整个堆空间——包括新生代和老年代。不幸的是,它实际上有点复杂和混乱。首先,很多MajorGC是由MinorGC触发的,所以在很多情况下无法将两个GC分开。另一方面,许多现代垃圾回收机制会清理部分永久代空间,因此使用术语“清理”只是部分正确。这使得我们不必关心它是称为MajorGC还是FullGC。需要注意当前GC是否停止了所有应用线程,或者是否可以在不停止应用线程的情况下并发处理。这种混淆甚至内置于JVM标准工具中。下面的例子很好地说明了我的意思。让我们比较两个不同工具的跟踪输出,ConcurrentMark和Sweep收集器(-XX:+UseConcMarkSweepGC)在JVM中运行。第一次尝试通过jstat输出:my-precious:me$jstat-gc-t42351sTimeS0CS1CS0US1UECEUOCOUMCMUCCSCCCSUYGCYGCTFGCFGCTGCT5.734048.034048.00.034048.0272640.0194699.71756416.0181419.918304.017865.12688.02497.630.27500.0000.2756.734048.034048.034048.00.0272640.0247555.41756416.0263447.918816.018123.32688.02523.140.35900.0000.3597.734048.034048.00.034048.0272640.0257729.31756416.0345109.819072.018396.62688.02550.350.45100.0000.4518.734048.034048.034048.034048.0272640.0272640.01756416.0444982.519456.018681.32816.02575.870.55000.0000.5509.734048.034048.034046.70.0272640.016777.01756416.0587906.320096.019235.12944.02631.880.72000.0000.72010.734048.034048.00.034046.2272640.080171.61756416.0664913.420352.019495.92944.02657.490.81000.0000.81011.734048.034048.034048.00.0272640.0129480.81756416.0745100.220608.019704.52944.02678.4100.89600.0000.89612.734048.034048.00.034046.6272640.0164070.71756416.0822073.720992.019937.13072.02702.8110.97800.0000.97813.734048.034048.034048.00.0272640.0211949.91756416.0897364.421248.020179.63072.02728.1121.08710.0041.09114.734048.034048.00.034047.1272640.0245801.51756416.0597362.621504.020390.63072.02750.3131.18320.0501.23315.734048.034048.00.034048.0272640.021474.11756416.0757347.022012.020792.03200.02791.015151.33620.0501.38616.734048.034048.034048.034047.00.0272640.048378.01756416.01756416.0片段.0501.484在JVM启动17秒后提取。根据这些信息,我们可以得出运行了12次MinorGC和2次FullGC的结果,总时间跨度为50毫秒。您可以使用基于GUI的工具(如jconsole或jvisualvm)获得相同的结果。java-XX:+PrintGCDetails-XX:+UseConcMarkSweepGCeu.plumbr.demo.GarbageProducer3.157:[GC(分配失败)3.157:[ParNew:272640K->34048K(306688K),0.0844702秒]272640K->69574K(20)63104K,0.0845560秒][时间:用户=0.23系统=0.03,实际=0.09秒]4.092:[GC(分配失败)4.092:[ParNew:306688K->34048K(306688K),0.1013723秒]342214K->136581K(4),0.1014307secs][Times:user=0.25sys=0.05,real=0.10secs]...为简洁起见...11.292:[GC(AllocationFailure)11.292:[ParNew:306686K->34048K(306688K),0.0857219secs]971599K->779148K(2063104K),0.0857875secs][Times:user=0.26sys=0.04,real=0.09secs]12.140:[GC(AllocationFailure)12.140:[ParNew:306688K->34046K(307.688K),17046K(307688K),17046Ksecs]1051788K->856120K(2063104K),0.0822400secs][Times:user=0.25sys=0.03,real=0.08secs]12.989:[GC(AllocationFailure)12.989:[ParNew:306686K->34048K(306.618K6),6秒]1128760K->931412K(2063104K),0.1087416secs][Times:user=0.24sys=0.04,real=0.11secs]13.098:[GC(CMSInitialMark)[1CMS-initial-mark:897364K(1756416K)]936667K(2063104K),0.0041705秒][Times:user=0.02sys=0.00,real=0.00secs]13.102:[CMS-concurrent-mark-start]13.341:[CMS-concurrent-mark:0.238/0.238secs][Times:user=0.36sys=0.01,真实=0.24秒]13.341:[CMS-concurrent-preclean-start]13.350:[CMS-concurrent-preclean:0.009/0.009secs][Times:user=0.03sys=0.00,real=0.01secs]13.350:[CMS-concurrent-abortable-preclean-start]13.878:[GC(分配失败)13.878:[ParNew:306688K->34047K(306688K),0.0960456秒]1204052K->1010638K(2063104K),0.0961542秒][Times9:用户=0.Times9:系统=0.04,实际=0.09秒]14.366:[CMS-concurrent-abortable-preclean:0.917/1.016秒][时间:用户=2.22系统=0.07,实际=1.01秒]14.366:[GC(CMS最后评论)[YG占用:182593K(306688K)]14.366:[重新扫描(并行),0.0291598秒]14.395:[弱参考进程ing,0.0000232秒]14.395:[类卸载,0.0117661秒]14.407:[擦除符号表,0.0015323秒]14.409:[擦除字符串表,0.0003221秒][1CMS-备注:976591K(1756416K)1),3(1756416K)16,36420]114591,80.0462010秒][时间:用户=0.14系统=0.00,实际=0.05秒]14.412:[CMS并发扫描开始]14.633:[CMS并发扫描:0.221/0.221秒][时间:用户=0.37系统=0.00,真实=0.22秒]14.633:[CMS-concurrent-reset-start]14.636:[CMS-concurrent-reset:0.002/0.002secs][Times:user=0.00sys=0.00,real=0.00secs]innod在同意这个结论之前,让我们看看从同一个JVM开始收集的垃圾收集日志的输出显然-XX:+PrintGCDetails告诉我们一个不同的和更详细的故事:根据这些信息,我们可以看到在12Minor之后GCs开始和上面的有点不一样。FullGC没有运行两次。不同的是,单次GC在永久代的不同阶段运行了两次:1.初始标记阶段耗时0.0041705秒,约4ms。此阶段暂停“停止世界”事件,停止所有应用程序线程,并开始标记。2.并行执行标记和清洁阶段。这些都是与应用程序线程并行的。3、最后的Remark阶段,耗时0.0462010秒,约46ms。这个阶段再次暂停所有事件。4.并行执行清理操作。顾名思义,这个阶段也是并行的,不会停止其他线程。所以,我们从垃圾回收日志可以看出,实际上只是一次MajorGC来清理旧空间,而不是两次FullGC。如果你在以后做决定,jstat提供的数据会指导你做出正确的决定。它正确列出的两种情况暂停了所有事件,导致所有线程停止总共50毫秒。但是如果你试图优化吞吐量,你就会被误导。该列表只列出了回收的initialmark和finalRemark阶段,jstat的输出看不到那些并发完成的作业。结论考虑到这种情况,最好避免用Minor、Major、FullGC术语来思考。相反,监控应用程序延迟或吞吐量,并将GC事件与结果相关联。当这些GC事件发生时,您需要格外注意某些信息,GC事件是否强制所有应用程序线程停止或并行处理某些事件。近期热点文章推荐:1.1,000+Java面试题及答案(2021最新版)2.别在满屏的if/else中,试试策略模式,真的很好吃!!3.操!Java中xx≠null的新语法是什么?4、SpringBoot2.5发布,深色模式太炸了!5.《Java开发手册(嵩山版)》最新发布,赶快下载吧!感觉不错,别忘了点赞+转发!