JVM年轻代标记复制过程深入分析

2020-09-07 10:34:04 作者: JVM年轻代

我们都知道Java对象存储在堆内存中,堆大小=年轻代+老年代。默认情况下,年轻代与老年代的堆大小比例为1:2,即-XX:NewRatio=2。年轻代结构

新创建的对象总是会放入年轻代中,年轻代分为Eden和两个Survivor区,Survivor区被命名为From和To以示区分,可以使用-Xmn2g来设置年轻代大小为2G。

默认情况下,Edem:From:To=8:1:1,即-XX:SurvivorRatio=8,Eden与一个Survivor区的比值。JVM每次只会使用Eden区和其中的一块Survivor区为对象服务,所以无论什么时候,总有一块Survivor区是空闲着的,年轻代实际可用的内存空间为9/10的年轻代空间。标记复制过程

年轻代采用标记复制算法进行垃圾回收,因为绝大部分对象都是朝生夕灭的,真正需要复制的对象非常少。复制算法就是把对象从一个区域复制到另外一个空白区域,因此不像老年代的标记清除算法,不存在内存碎片的问题。

(1)新创建的对象总是放入Eden区,当Eden区满后会触发MinorGC(YoungGC)。当执行MinorGC时,把Eden和From区中存活的对象复制到To区,然后清空Eden和From区,并把这些对象的年龄设置为1。

(2)新创建的对象仍然进入Eden区,当Eden区满后会触发MinorGC(YoungGC)。这时把Eden和To区中存活的对象复制到From区,然后清空Eden和To区,并把这些对象的年龄设置为2,依次循环下去。

之后对象在 Survivor 区每熬过一次MinorGC,对象的年龄+1,当对象的年龄达到15时(最大值15,对象头里面4位表示年龄。可以通过参数-XX:MaxTenuringThreshold设定),这些对象就会晋升到老年代。但是对于一些较大的对象,即需要分配一块较大的连续内存空间的则是直接晋升到老年代(可以通过参数-XX:PretenureSizeThreshold设定),大于这个设置值的对象直接晋升到老年代,目的是避免在Eden区及两个Survivor区之间发生大量的对象复制。

为什么有两个Survivor区

(1)如果没有Survivor区,每执行一次MinorGC,活着的对象全部进入老年代,老年代很快被填满;

(2)如果只有一个Survivor区,缺点:MinorGC频率增加;浪费50%的内存空间。

MinorGC频率增加:当执行第一次MinorGC时,Eden区中活着的对象复制到Survivor区,然后清空Eden区,那么在执行第二次MinorGC前,对象都被分配到Survivor区,由于Survivor区比较小,很快就会触发第二次MinorGC。即使Eden和Survivor对半分,也是50%触发Minor GC,而有两个Survivor达到80%才触发MinorGC,MinorGC的频率大大降低。

浪费50%的内存空间:标记复制算法必须保证有一块空闲的内存空间供对象复制,如果只有一个Survivor区,假如Eden占80%、Survivor占20%,第一次MinorGC内存空间只浪费了20%,但第二次的时候,会浪费80%,无论如何都会浪费50%的内存空间,而两个Survivor区只浪费10%的内存空间,因为只有一个Survivor区是空的。