zoukankan      html  css  js  c++  java
  • 探索G1垃圾回收器

     

    前言

    最近王子因为个人原因有些忙碌,导致文章更新比较慢,希望大家理解,之后也会持续和小伙伴们一起共同分享技术干货。

    上篇JVM的文章中我们对ParNew和CMS垃圾回收器已经有了一个比较透彻的认识,感兴趣的小伙伴可以去回看一下探索ParNew和CMS垃圾回收器

    今天我们继续探索垃圾回收器G1的原理,让我们开始吧!

    G1的内存模型

    G1是从jdk7开始出现的,在jdk9中被设为默认垃圾收集器,目标就是彻底替换掉CMS,那么为什么它可以替换掉CMS呢?

    首先我们就来看看它的内存模型吧。

    其实G1是可以同时回收年轻代和老年代的,他最大的特点就是把jvm堆内存拆分为了多个大小相等的Region,那么还存在年轻代和老年代吗?

    答案是肯定的,不同的是新生代可能包含了某些Region,老年代也可能包含了某些Region,如下图:

    到底有多少Region?每个Region有多大呢?

    其实这个默认情况下是自动计算的,假如我们给定整个堆内存大小为4096M,然后使用“-XX:+UseG1GC”指定垃圾回收器为G1,此时会自动用堆内存大小除以2048,因为JVM最多可以有2048个Region,然后Region的大小必须是2的倍数。

    堆内存为4096M,就会分配给每个Region 2M的内存空间。我们使用G1默认的计算方式就可以了。

    当然也可以通过参数“-XX:G1HeapRegionSize”来指定Region的大小。

    新生代和老年代的默认比例是多少呢?

    我们知道使用ParNew和CMS垃圾回收器时,新生代和老年代的默认比例是1:2,而使用G1后,默认新生代对堆内存的初始占比是5%,这个可以通过“-XX:G1NewSizePercent”来设置初始占比,一般不需要设置。

    细心的小伙伴会发现,这里说的占比是初始占比,因为系统运行的时候,JVM其实会不停的给新生代增加更多的Region,但是最多新生代的占比不会超过60%,可以通过“-XX:G1MaxNewSizePercent”来设置。

    而一旦发生了垃圾回收,新生代的Region数量还会减少,所以其实新生代和老年代的占比不是一成不变的,而是动态改变的。

    新生代还有eden和survivor吗?

    答案是肯定的,新生代还是有eden和survivor的,只不过内存占用会随着Region的增多而增大。

    G1的停顿时间控制

    除了内存的变化,G1还有一个最大的变化,就是可以让我们设置一个垃圾回收的预期停顿时间,也就是说我们可以指定G1垃圾回收导致“Stop the World”的最长时间。

    我们知道JVM一大痛点就是"Stop the World",尽量减少它的时间就可以做到JVM的优化。

    引入G1后,我们可以自己去设定这个停顿的最长时间了,相当于直接控制了垃圾回收的性能。

    G1要做到这一点就要去追踪每个Region的回收价值,那什么是回收价值呢?大家看下图:

    比如两个Region中,其中一个有10M的垃圾对象,垃圾回收需要耗时1s,另一个有20M的垃圾对象,垃圾回收耗时200ms。

    然后G1进行垃圾回收的时候,发现最近1小时垃圾回收已经导致了几百毫秒的系统停顿了,所以会选择回收价值高的Region进行回收,200ms的时间就能回收掉20M的垃圾对象,回收价值相对较高,所以会选择这个Region进行回收

    G1控制停顿时间的思路,简单来讲就是,它会通过跟踪Region的回收价值,尽可能的保证系统停顿时间在你设定的停顿时间范围内。

    G1的垃圾回收详解

    上文我们了解到新生代还是有eden和survivor的,那么随着新生代占据堆内存大小的60%的时候,这个时候就会触发新生代的GC,G1也会使用之前我们说过的复制算法进行垃圾回收,进入一个“Stop the World”状态。

    但是这个过程与之前的Minor GC其实是有差别的,首先回收的对象变成了带有垃圾对象的Region,然后回收的同时会根据设定的停顿时间进行价值回收,如上文所述。

    什么时候进入老年代呢?

    这个可以说和之前是一模一样的,简单介绍如下:

    新生代躲过多次垃圾回收后会进入老年代;

    GC后存活对象超过Survivor区的50%,那么会触发动态年龄判定规则,符合规则的进入老年代。

    具体细节不在说明,可以参考王子之前的文章秒懂JVM的垃圾回收机制,有详细解释。

    需要注意的是,G1的大对象不是存到老年代中的,而是提供了专门的Region来存放大对象。

    在G1中,大对象的判断规则就是这个对象超过了一个Region大小的50%,比如Region是2M的,那如果你的对象超过了1M,就会被认定为大对象,做特殊处理。

    而且如果这个大对象过大,可以横跨多个Region进行存储,如下图:

    老年代具体又是怎么进行垃圾回收的呢

    这个过程说起来可能稍微复杂了一点,但是它和CMS的垃圾回收过程其实是类似的。关于CMS的垃圾回收的几个阶段可以回顾王子的上篇文章探索ParNew和CMS垃圾回收器

    首先我们要弄明白,什么时候会触发新生代和老年代的混合垃圾回收?

    G1有一个参数“-XX:InitiatingHeapOccupancyPercent”,默认值为45%。

    什么意思呢?就是说当老年代占据了堆内存的45%的Regionf的时候,就会触发混合垃圾回收。

    具体流程是什么样的呢?

    首先会触发一次初始标记操作,这个过程是要“Stop the World”的,对应的就是CMS的初始标记阶段,细节不再说明。

    接着会进入并发标记阶段,这个阶段同样对应CMS的并发标记阶段,不再说明。

    接着会进入最终标记阶段,这个阶段其实和CMS的重新标记阶段也基本一致。

    最后就是混合回收阶段,这个阶段和CMS的并发清理阶段就不太一样了,这个阶段会计算每个Region中的存活对象数量,存活数量占比,还有执行垃圾回收的耗时等问题。

    接着会进入“Stop the World”阶段,然后全力以赴进行垃圾回收,并尽量保证停止时间不超过我们设定好的时间,所以可能只会回收掉之前标记好的一部分垃圾对象。

    为什么要叫做混合回收呢,因为它不仅仅回收的是老年代,新生代和大对象的Region也会同时进行回收,而具体回收哪些Region就要视情况而定了,根据价值回收价值G1会自己做出选择。

    而混合回收是可以进行多次的,比如先停止系统,混合回收掉一部分Region,再停止系统,再执行一次混合回收。

    有参数可以控制这个数量,“-XX:G1MixedGCCountTarget”参数,就是在一次混合回收的过程中,最后一个阶段执行几次,默认是8次。

    为什么要这样反复多次的回收呢?

    因为这样每次回收停止系统的时间都很短,在回收的间隙系统是可以正常运行的。

    还有个参数“-XX:G1HeapWastePercent”,默认值是5%。

    它的意思是,混合回收的时候,都是基于复制算法进行的,把Region存活的对象放入其他Region,然后清除掉本来的Region。那么当空闲的Region数量达到堆内存的5%,就会立即停止混合回收。

    而通过这种复制算法回收,也不会出现像CMS标记清理算法导致的内存碎片问题。

    还有个参数“-XX:G1MixedGCLiveThresholdPercent”,默认值是85%,意思就是回收Region的时候,存活的对象必须少于85%才可以被回收掉。否则存活对象太多,复制的时候成本是很高的。

    如果回收失败怎么办?

    如果在复制的时候发现没有空闲的Region可以承载存活的对象,那么会触发失败,立马停止系统进程,采用单线程进行标记、清理和压缩整理,空闲出一批Region,这个过程是极慢的。

    总结

    本文我们对G1的内存机制和垃圾回收的算法做了一个比较清晰的解释。

    阅读完本文,相信小伙伴们自己可以总结出G1和CMS究竟有什么不一样了吧。

    欢迎小伙伴们留言区讨论G1和CMS的区别,王子会第一时间回复。

    那我们下篇文章再见。

    往期文章推荐:

    大白话谈JVM的类加载机制

    JVM内存模型不再是秘密

    轻松理解JVM的分代模型

    秒懂JVM的垃圾回收机制

    探索ParNew和CMS垃圾回收器

  • 相关阅读:
    你要结婚了丶
    20150806这是个悲伤的日子。
    今天是我的生日。
    这是从word发的第一篇博客。
    【关于JavaScript】自动计算的实例
    【关于JavaScript】常见表单用户名、密码不能为空
    【关于php】Appserv中关于DW配置站点问题
    【关于php】Appserv的安装注意事项
    【无题】写在5月18日
    HTML中如何添加日历插件(JQUERY)
  • 原文地址:https://www.cnblogs.com/lm970585581/p/13926795.html
Copyright © 2011-2022 走看看