面试问题
昨天面试的时候,面试官问的问题:
- 什么是老年代空间担保机制?担保的过程是什么?
- 老年代空间担保机制是谁给谁担保?
- 为什么要有老年代空间担保机制?或者说空间担保机制的目的是什么?
- 如果没有老年代空间担保机制会有什么不好?
下面我们就带着这些问题去了解一下JVM老年代空间担保机制吧。
老年代空间担保机制
如图,在每次MinorGC之前,老年代可用空间会和新生代所有对象的总大小进行对比,如果老年代可用空间大于新生代所有对象的总大小,就可以直接进行MinorGC,因为即使MinorGC后所有对象都活着,S区放不下,也可以放到老年代中。如果老年代的可用空间小于新生代所有对象的总大小,就会进行下一个判断,判断老年代的可用空间和新生代每次MinorGC后进入老年代对象的平均大小,如果发现老年代的可用内存大于每次MinorGC后进入老年代对象的平均大小(举个例子:之前每次Minor GC后,平均都有10MB左右的对象会进入老年代,那么此时老年代可用内存大于10MB。这就说明很可能这次Minor GC过后也是差不多10MB左右的对象会进入老年代,此时老年代空间是够的),那么可以尝试进行MinorGC,但是有可能出现MinorGC后所有对象都存活,全部进去老年代,导致老年代可用空间不足,(例:老年代剩余100M,新生代中对象总大小为200M,但是每次MinorGC后平均进入老年代的对象大小为80M,但是此次MinorGC后所有对象全部存活,导致老年代可用空间不足)此时就会触发FullGC。反之,如果老年代的可用空间小于每次MinorGC后进入老年代对象的平均大小,则会触发FullGC。如果FullGC之后,老年代也没有足够的可用空间存放新生代的对象,则会出现OOM内存溢出。
了解了这个担保流程之后,我们再来看我们的面试题:
- 什么是老年代空间担保机制?担保的过程是什么?
上面文字解释的就是。 - 老年代空间担保机制是谁给谁担保?
我理解的是老年代给新生代的S区做担保。 - 为什么要有老年代空间担保机制?或者说空间担保机制的目的是什么?
目的:避免频繁的进行FullGC。 - 如果没有老年代空间担保机制会有什么不好?
如果没有这个担保机制,就会直接执行Full GC,这样对性能的影响频次会增加。
哪些对象会进行老年代
- 动态对象年龄判断:当前放对象的Survivor区域里,一批对象的总大小大于了这块Survivor区域的内存大小的50%,那么此时大于等于这批对象年龄的对象,就可以直接进入老年代了。
- 大对象直接进入老年代。
- 15次Minor GC后还存活的对象。
- Minor GC后的对象太多无法放入Survivor区。
Full GC的触发时机
- 老年代空间不足。比如大对象或者年龄超过15的对象进入老年代。
- 空间分配担保失败。这个就是咱们今天说的这种情况。老年代可用内存小于新生代全部对象的大小,如果没开启空间担保参数,会直接触发Full GC,所以一般空间担保参数都会打开。
- Concurrent Mode Failure 执行CMS GC的过程中同时有对象要进入老年代,而此时老年代空间不足(可能是GC过程中浮动垃圾过多导致暂时性的空间不足)便会报Concurrent Mode Failure错误并触发Full GC。这种情况下会使用Serial Old收集器,也就是发生了退化,CMS退化为serial gc,Serial Old收集器是单线程的,对GC的影响很大。concurrent mode failure产生的原因是老年代剩余的空间不够,导致了和gc线程并发执行的用户线程创建的大对象(由PretenureSizeThreshold控制新生代直接晋升老年代的对象size阀值)不能进入到老年代,只要stop the world来暂停用户线程,执行GC清理,单线程对全堆以及 metaspace 进行回收,STW 的时间会特别长,对业务系统的可用性影响比较大。可以通过设置CMSInitiatingOccupancyFraction预留合适的CMS执行时剩余的空间。
- 老年代可用内存小于历次新生代GC后进入老年代的平均对象大小,此时会提前Full GC;但是"-XX:HandlePromotionFailure"参数,在JDK 1.6以后就被废弃了,所以现在一般都不会在生产环境里设置这个参数了。在JDK 1.6以后,只要判断"老年代可用空间"大于"新生代对象总和"或者"老年代可用空间"大于"历次Minor GC升入老年代对象的平均大小",两个条件满足一个,就可以直接进行Minor GC,不需要提前触发Full GC了。
- 新生代Minor GC后的存活对象大于Survivor,那么就会进入老年代,此时老年代内存不足,触发Full GC。这里的不足就是判断条件后还是不足或者经过判断后进行YGC后放入老年代此时的空间不足,然后进行Full GC,就会出现频繁Full GC。达到一定情况后,就会OOM了。
- 如果用的是CMS收集器,老年代可用内存大于历次新生代GC后进入老年代的对象平均大小,但是老年代已经使用的内存空间超过了("-XX:CMSInitiatingOccupancyFaction=92%"JDK6默认值 )这个参数指定的比例,也会自动触发Full GC。
- 未指定老年代和新生代大小,堆伸缩时会触发Full GC。
- 调用System.gc(),只是建议虚拟机执行Full GC,但虚拟机不一定真正去执行。