• JVM学习----垃圾回收--G1


    Garbage First
    2004论文发布 ----> 2009 JDK 6u14 体验 ----> 2012 JDK 7u4 官方支持 ----> 2017 JDK9 默认

    适用场景:

    • 同时注重吞吐量和低延时,默认的暂停目标200ms
    • 超大堆内存,会将堆划分为多个大小相等Region
    • 整体上是标记整理算法,两个区域之间是复制算法

    相关JVM参数:
    -XX:+UseG1GC
    -XX:G1HeapRegionSize=size
    -XX:MaxGCPauseMillis=time

    G1 垃圾回收阶段

    G1三个循环回收阶段

    在这里插入图片描述

    1、Young Collection (新生代)

    会STW
    1)、将内存换分为多个区域,E代表Eden
    在这里插入图片描述
    2)、当Eden区域逐渐被占满,触发 Minor GC,采用复制算法,将幸存的对象复制到幸存区。S:代表幸存区。
    在这里插入图片描述
    3)、在经过一段时间之后,幸存区逐渐被占满,那么将年龄够的对象晋升到老年代。将不符合年龄要求的对象,拷贝到新的幸存区。
    在这里插入图片描述

    2、Young Collection + CM

    在Young GC时会进行GC Root的初始标记(找到根对象,STW时进行,在上一个阶段执行)
    老年代占用堆空间比例达到阈值时,会进行并发标记(根据根对象的引用链,找到其他对象,不会STW),由下面的JVM参数决定
    -XX:InitiatingHeapOccupancyPercent=percent ( 默认45% )
    在这里插入图片描述

    3、Mixed Collection

    会对 E、S、O 进行全面的垃圾回收
    最终标记(Remark)会STW,因为并发标记可能有遗漏
    拷贝存活(Evacuation)会STW
    -XX:MaxGCPauseMillis=ms (最大暂停时间)

    对于新生代,将Eden区的幸存对象拷贝到幸存区,将其他幸存区里面的对象,该晋升的晋升,该继续保留的保留。其他的就全部进行回收了。
    对于老年代,因为老年代会很大,默认45%,那么根据我们最大暂停时间,可能在这设置的时间内,我们不能讲全部老年区进行判断回收,那么系统就会判断,那么回收等级高,就优先回收哪些优先等级高的。
    在这里插入图片描述

    Full GC

    在这里插入图片描述
    CMS,在碎片少的时候,是并发执行垃圾回收的,如果要是并发失败,会退化为 Serial GC,那么这种产生的GC 才叫Full GC
    G1 ,当你垃圾产生的速度 小于 垃圾回收的速度,这不叫Full GC。 当你垃圾产生的速度 大于 垃圾回收的速度,才会产生 Full GC,并发回收失败,进行并行回收。

    Young Collection 跨代引用问题

    新生代回收的跨代引用(老年代引用新生代)问题

    当新生代对象被老年代对象引用,我们将老年代再进行细分,每个大约512k大小。使用卡表技术
    如果这个老年代引用了新生代对象,那么这个卡,我们就标记为脏卡。以后我们就不用遍历整个老年代,减少搜索范围,提高GC Root的寻找效率。
    新生代会记录脏卡,那么在 新生代垃圾回收时,就会根据Eden记录脏卡区域进行遍历查询GC Root
    在这里插入图片描述

    Remark 重新标记

    在并发标记过程中,产生了新的引用,原本应该被回收的对象被引用了。但是并发标记并没有对 被引用的对象进行重新标记。

    所以重新标记就是为了解决这种情况。
    具体做法,在并发标记时:当对象引用 发生改变时,JVM就会对该对象加入一个 写屏障 (只要对象引用发生了改变,就会触发写屏障)。
    写屏障:会将该对象加入一个队列(satb_mark_queue队列)之中,将该对象变为正在处理中的状态,进入重新标记阶段。
    重新标记,会STW,将队列中的 对象取出来,进行再一次的判断出来,判断队列中的对象是回收还是保留。

    G1 垃圾回收器的优化(8->9的优化)

    JDK 8u20 字符串去重

    优点:节省大量内存
    去电:略微多占用cpu时间,新生代回收时间略微增加
    -XX:+UserStringDeduplication

    jdk8 中字符串的存储底层实际采用的char数组进行存储,new两个,那么也是存两个char数组

    String s1 = new String("hello"); // char[]{'h','e','l','l','o'}
    String s2 = new String("hello"); // char[]{'h','e','l','l','o'}
    
    • 1
    • 2
    • 将所有新分配的字符串放入一个队列
    • 当新生代回收时,G1并发检查是否有字符串重复
    • 如果它们值相同,让它们引用同一个char[] // 例如上面代码,s1,s2 引用同一个
    • 主义,与String.intern() 不一样
      • String.intern() 关注的是字符串对象 // 用的StringTable去重,是用的常量池,这个针对的是对象
      • 而字符串去重关注的是char[]
      • 而JVM内部,使用了不同的字符串表

    JDK 8u40 并发标记类卸载

    所有对象都经过并发标记后,就能知道哪些类不再被使用,当一个类加载器的所有类都不再被使用,则卸载它所加载过的所有类。
    例如,很多框架都是用自定义加载器。
    -XX:+ClassUnloadWithConcurrentMark // 默认启用

    JDK 8u60 回收巨型对象

    • 一个对象大于region的一半时,称之为巨型对象(你也可以占多个region)
    • G1不会对巨型对象进行拷贝
    • 回收时,巨型对象优先考虑
    • G1会跟踪老年代所有incoming引用,这样老年代incoming引用为0的巨型对象就可以在新生代垃圾回收时处理掉。

    JDK 9 并发标记起始时间的调整

    • 并发标记必须在堆空间占满前完成,否则退化为FullGC
    • JDK 9 之前需要使用 -XX:InitiatingHeapOccupancyPercent
    • JDK 9 可以动态调整
      • -XX:InitiatingHeapOccupancyPercent 用来设置初始值,默认45%
      • 进行数据采用并动态调整 — > 阈值
      • 总会添加一个安全的空档空间

    这样可以尽可能避免 G1 GC 退化为 FullGC

  • 相关阅读:
    使用libevent实现回显服务器
    angr原理与实践(二)—— 各类图的生成(CFG CG ACFG DDG等)
    Java子类继承父类私有方法属性问题讲解
    VS Code + Remote-ssh插件实现远程开发
    数据结构:队列
    Docker最新超详细教程——Docker创建运行MySQL并挂载
    【解决】如何在JavaScript中终止forEach循环
    TypeScript入门
    Django多条件筛选查询
    强大的Nginx配置生成器
  • 原文地址:https://blog.csdn.net/m0_48639280/article/details/126058790