G1垃圾回收器原理

现在一般的垃圾回收器都是选择ParaNew+CMS的搭配,使用多线程并发回收可以有效降低STW(stop the world)时间,但是在一些响应时间比较短的场景下,STW的时间不可控就会造成一些影响,还有就是系统的机器内存很大的情况下,可能一次回收会有很多个G的垃圾需要回收,那么必然会导致垃圾回收时间较长。在这种情况下,就需要使用G1垃圾回收器,G1可以同时完成年轻代和老年代的回收,且可以指定我们所需要的STW的时间,防止系统长时间的卡顿。

G1把java堆内存拆分为了多个大小相等的Region,同时G1也存在新生代和老年代的逻辑概念,所以新生代和老年代都会包含一部分的Region。我们对于JVM垃圾回收的各种优化其实都是为了减少STW时间对于我们系统的影响,G1最大的特点就是可以设置一个预期的停顿时间,这样就相当于我们可以直接控制STW的时间,这就厉害了。

那么G1是如何做到控制垃圾回收的停顿时间的呢,G1会追踪所有Region的回收价值,回收价值的意思就是这个Region中包含了多少的垃圾对象,回收这些垃圾对象需要耗费多少的时间。在垃圾回收的过程中,G1通过我们设置的预期停顿时间,追踪每个Region的回收价值,在有限的时间内,去尽可能的多回收一些垃圾对象。

通过 -XX:+UseG1GC 参数就可以指定使用G1垃圾回收器了,G1里面的Region的数量是根据堆内存的大小自动计算的,默认情况下是堆内存/2048,假如我们的堆内存是4G,那么每个Region的大小就是2M,当然也可以通过参数-XX:G1HeapRigionSize 指定每个Rigion大小,一般使用默认的就好。

刚开始的时候,新生代所占内存默认是堆内存的5%,也可以通过-XX:G1NewSizePercent 指定G1初始化新生代的大小,系统在运行的过程中,不断给新生代分配越来越多的对象,新生代所占的Region也越来越多,但是不会超过堆内存的60%,这个也可以通过-XX:G1MaxNewSizePercent 参数指定G1新生代对堆内存最大占比。G1中的Region都是动态分配的,没有固定的说是新生代还是老年代,可能一个Region现在存放的年轻代的对象,过一会又变成了存放老年代的对象。

G1中的新生代也存在Eden区和Survivor区的概念,当需要触发新生代的垃圾回收时,使用的是复制算法,根据我们预设的停顿时间进行垃圾回收,可以通过 -XX:MaxGCPauseMills 参数设置,默认为200ms,然后清空Eden和一个Survivor区的Region,将存活的对象放入另一个Survivor区的Region中。

新生代垃圾回收之后,可能就会有一部分的存活对象需要进入到老年代,一般有两个条件,一个是经历了很多次Young GC,存活了一定岁数的对象,可以通过-XX:MaxTenuringThreshold参数设置,默认是15;另一个是动态年龄判定规则,一旦存活的对象超过了Survivor区的50%,比如1岁,2岁,3岁的存活对象大小大于Survivor区的50%,就把年龄大于3岁的对象都放入老年代中。

还有一种特殊的情况,在之前的ParaNew+CMS垃圾回收器中,如果年轻代的存活对象大于Survivor区的大小,则会直接进入老年代,但是在G1中,这种情况会有专门的Region来存放大对象,不需要再放入老年代中了,如果大对象的大小大于一个Region的大小的话,则会跨多个Region存放,在新生代,老年代垃圾回收的时候,会顺带着把大对象的Region一起回收。

G1中最重要的概念就是Region,所有的对象都是存放在Region中,进行动态分配,不需要我们手动去设置各种参数来调节,我们只需要设置我们能接受的停顿时间并告诉G1,G1就可以在指定时间内,通过每个Region的回收价值,来最大程度的进行垃圾回收,保证不会影响系统的正常运行。

你可能感兴趣的:(JVM)