1. 新生代里的对象一般在什么场景下会进入老年代?
代码在运行的过程中,会不断的创建各种各样的对象,这些对象都会优先放到新生代的 Eden区和 Survivor1区。
接着假如新生代的 Eden区和 Survivor1区都快满了,此时就会触发Minor GC,把存活对象转移到 Survivor2区去。如下图所示:
然后接着会使用 Eden区和Survivor2区,来分配新的对象,如下图所示:
2. 躲过15次GC之后进入老年代
在上面的流程中,随着系统的运行,新生代逐渐满了,就会触发Minor GC,此时就有少量的存活对象转移到空着的Survivor区中。
然后系统机型运行,继续在Eden区里分配各种对象。
部分强引用对象每次在新生代里躲过一次GC被转移到一块Survivor区域中,此时他的年龄就会增长一岁。
默认的设置下,当对象的年龄达到15岁的时候,也就是躲过15次GC的时候,他就会转移到老年代里去。
这个具体是多少岁进入老年代,可以通过JVM参数“-XX:MaxTenuringThreshold” 来设置,默认是15岁。
3. 动态对象年龄判断
除了对象年龄规则外,还有其他规则可以让对象进入老年代,不用等待15次GC过后才可以。
这个规则是,假如当前放对象的Survivor区域里,一批对象的总大小大于了这块Survivor区域的内存大小的 50%,那么此时大于等于这批对象年龄的对象,就可以直接进入老年代了。
案例说明,假设这个图里的Survivor2区有两个对象,这两个对象的年龄一样都是2岁,且两对象加起来超过了50MB,也就是Survivor2区的50%。那么这时候,Survivor2区里的大于等于2的对象,就要全部进入老年代里去。
这就是所谓的动态年龄判断的规则,这条规则也会让一些新生代的对象进入老年代。
4. 大对象直接进入老年代
有一个JVM参数,就是 “-XX:PertenureSizeThreshold”,可以把它的值设置为字节数,比如“1048576” 字节,就是 1MB。
它的意思是,如果你创建了一个大于这个值大小的对象,比如数组或集合,就会直接把这个大对象放到老年代里去。而不会经过新生代。
这么做,是为了避免新生代出现大对象后,频繁进行GC回收复制后才能进入老年代。这样会很耗费时间和导致频繁GC。
5. Minor GC后的对象太多无法放入 Survivor区怎么办?
如果在Minor GC之后发现剩余的存活对象太多了,没办法放入另外一块 Survivor 区,就会直接把这些对象直接转移到老年代去。
如上图,Eden区剩余150MB对象,会直接放入到老年代。
6. 老年代空间分配担保原则
如果新生代有大量对象存活下来,Survivor区内存不够放不下了,必须转移到老年代去。如果此时老年代里空间也不够放这些对象,该怎么办?
首先,在执行Minor GC之前,JVM会先检查一下老年代可用的内存空间,是否大于新生代所有对象的总大小。
该检查,是为了保证极端环境下,新生代Minor GC过后存活下来要转移到老年代的所有对象都能转移到老年代里。
如果发现老年代的内存大小是大于新生代所有对象的,就可以对新生代进行Minor GC了。即使Minor GC之后所存活的对象在Survivor区放不下,也可以转移到老年代去。
如果执行Minor GC之前发现老年代的可用内存小于新生代的全部对象大小。此时在可能发生Minor GC后,新生代的存活对象全部存活,需要全部转移到老年代去,但是老年代又不够?
这里有个参数,在Minor GC之前,发现老年代的可用内存小于新生代的全部对象大小,就会看一个 “ -XX:-HandlePromotionFailure” 的参数是否设置了。
下一步判断,如果设置了该参数,就会看看老年代的内存大小,是否大于之前每一次 Minor GC后进入老年代的对象的平均大小。
如果该判断失败,或者是 “-XX:HandlePromotionFailure” 参数没设置,就会直接触发一次 “Full GC” ,就是对老年代进行垃圾回收,尽量腾出来一些内存空间,然后再执行Minor GC。
如果上面两个步骤都判断成功了。就说明可以尝试一下 Minor GC。此时进行 Minor GC有几种可能。
第一种,Minor GC过后,剩余的存活对象的大小,是小于 Survivor区的大小,那么此时存活对象进入 Survivor 区域即可。
第二种,Minor GC过后,剩余的存活对象的大小是大于 Survivor区域的大小,但是是小于老年代可用内存大小的,此时就直接进入老年代即可。
第三种,Minor GC过后,剩余的存活对象的大小,大于Survivor区域的大小,也大于老年代的可用内存区域的大小。此时老年代就放不下这些存活对象,就会发生 “Handle Promotion Failure” 的情况,这个时候就会触发一次 “Full GC” 。
Full GC就是对老年代进行垃圾回收,同时也一般会对新生代进行垃圾回收。
如果要是 Full GC过后,老年代还是没有足够的空间存放 Minor GC过后的剩余存活对象,那么此时就会导致所谓的 “OOM” 内存溢出了。
7. 老年代垃圾回收算法
简单来说,对老年代触发垃圾回收的时机,一般就是两个:
要么在Minor GC之前,通过检测发现很可能Minor GC之后要进入老年代的对象太多,老年代放不下,此时需要提前触发 Full GC然后再带着进行 Minor GC。
要么在Minor GC之后,发现剩余对象太多放入老年代都放不下了。
这里对老年代进行垃圾回收采用的是 标记整理算法。
所谓标记整理算法,就是先标记出来老年代当前存活的对象;接着让这些存活对象在内存里进行移动,把存活对象尽量都挪动到一边去,让存活对象紧凑的靠在一起,避免垃圾回收过后出现过多的内存碎片。
然后再一次性把垃圾对象都回收掉。
要注意的是,老年代的垃圾回收算法的速度至少比新生代的垃圾回收算法的速度慢10倍。
如果系统频繁出现老年代的 Full GC 垃圾回收,会导致系统性能被严重影响,出现频繁卡顿的情况。