当前位置: 首页 > article >正文

JVM之垃圾回收器G1概述的详细解析

G1(并发)
G1 特点

G1(Garbage-First)是一款面向服务端应用的垃圾收集器,应用于新生代和老年代、采用标记-整理算法、软实时、低延迟、可设定目标(最大 STW 停顿时间)的垃圾回收器,用于代替 CMS,适用于较大的堆(>4 ~ 6G),在 JDK9 之后默认使用 G1

G1 对比其他处理器的优点:

  • 并发与并行:

    • 并行性:G1 在回收期间,可以有多个 GC 线程同时工作,有效利用多核计算能力,此时用户线程 STW

    • 并发性:G1 拥有与应用程序交替执行的能力,部分工作可以和应用程序同时执行,因此不会在整个回收阶段发生完全阻塞应用程序的情况

    • 其他的垃圾收集器使用内置的 JVM 线程执行 GC 的多线程操作,而 G1 GC 可以采用应用线程承担后台运行的 GC 工作,JVM 的 GC 线程处理速度慢时,系统会调用应用程序线程加速垃圾回收过程

  • 分区算法

    • 从分代上看,G1 属于分代型垃圾回收器,区分年轻代和老年代,年轻代依然有 Eden 区和 Survivor 区。从堆结构上看,新生代和老年代不再物理隔离,不用担心每个代内存是否足够,这种特性有利于程序长时间运行,分配大对象时不会因为无法找到连续内存空间而提前触发下一次 GC

    • 将整个堆划分成约 2048 个大小相同的独立 Region 块,每个 Region 块大小根据堆空间的实际大小而定,整体被控制在 1MB 到 32 MB之间且为 2 的 N 次幂,所有 Region 大小相同,在 JVM 生命周期内不会被改变。G1 把堆划分成多个大小相等的独立区域,使得每个小空间可以单独进行垃圾回收

    • 新的区域 Humongous:本身属于老年代区,当出现了一个巨型对象超出了分区容量的一半,该对象就会进入到该区域。如果一个 H 区装不下一个巨型对象,那么 G1 会寻找连续的 H 分区来存储,为了能找到连续的 H 区,有时候不得不启动 Full GC

    • G1 不会对巨型对象进行拷贝,回收时被优先考虑,G1 会跟踪老年代所有 incoming 引用,这样老年代 incoming 引用为 0 的巨型对象就可以在新生代垃圾回收时处理掉

    • Region 结构图:

  • 空间整合:

    • CMS:标记-清除算法、内存碎片、若干次 GC 后进行一次碎片整理

    • G1:整体来看是基于标记 - 整理算法实现的收集器,从局部(Region 之间)上来看是基于复制算法实现的,两种算法都可以避免内存碎片

  • 可预测的停顿时间模型(软实时 soft real-time):可以指定在 M 毫秒的时间片段内,消耗在 GC 上的时间不得超过 N 毫秒

    • 由于分块的原因,G1 可以只选取部分区域进行内存回收,这样缩小了回收的范围,对于全局停顿情况也能得到较好的控制

    • G1 跟踪各个 Region 里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间,通过过去回收的经验获得),在后台维护一个优先列表,每次根据允许的收集时间优先回收价值最大的 Region,保证了 G1 收集器在有限的时间内可以获取尽可能高的收集效率

    • 相比于 CMS GC,G1 未必能做到 CMS 在最好情况下的延时停顿,但是最差情况要好很多

G1 垃圾收集器的缺点:

  • 相较于 CMS,G1 还不具备全方位、压倒性优势。比如在用户程序运行过程中,G1 无论是为了垃圾收集产生的内存占用还是程序运行时的额外执行负载都要比 CMS 要高

  • 从经验上来说,在小内存应用上 CMS 的表现大概率会优于 G1,而 G1 在大内存应用上则发挥其优势,平衡点在 6-8GB 之间

应用场景:

  • 面向服务端应用,针对具有大内存、多处理器的机器

  • 需要低 GC 延迟,并具有大堆的应用程序提供解决方案


记忆集

记忆集 Remembered Set 在新生代中,每个 Region 都有一个 Remembered Set,用来被哪些其他 Region 里的对象引用(谁引用了我就记录谁)

  • 程序对 Reference 类型数据写操作时,产生一个 Write Barrier 暂时中断操作,检查该对象和 Reference 类型数据是否在不同的 Region(跨代引用),不同就将相关引用信息记录到 Reference 类型所属的 Region 的 Remembered Set 之中

  • 进行内存回收时,在 GC 根节点的枚举范围中加入 Remembered Set 即可保证不对全堆扫描也不会有遗漏

垃圾收集器在新生代中建立了记忆集这样的数据结构,可以理解为它是一个抽象类,具体实现记忆集的三种方式:

  • 字长精度

  • 对象精度

  • 卡精度(卡表)

卡表(Card Table)在老年代中,是一种对记忆集的具体实现,主要定义了记忆集的记录精度、与堆内存的映射关系等,卡表中的每一个元素都对应着一块特定大小的内存块,这个内存块称之为卡页(card page),当存在跨代引用时,会将卡页标记为 dirty,JVM 对于卡页的维护也是通过写屏障的方式

收集集合 CSet 代表每次 GC 暂停时回收的一系列目标分区,在任意一次收集暂停中,CSet 所有分区都会被释放,内部存活的对象都会被转移到分配的空闲分区中。年轻代收集 CSet 只容纳年轻代分区,而混合收集会通过启发式算法,在老年代候选回收分区中,筛选出回收收益最高的分区添加到 CSet 中

卡表和操作系统内存页一个道理

  • CSet of Young Collection

  • CSet of Mix Collection


工作原理

G1 中提供了三种垃圾回收模式:YoungGC、Mixed GC 和 Full GC,在不同的条件下被触发

  • 当堆内存使用达到一定值(默认 45%)时,开始老年代并发标记过程

  • 标记完成马上开始混合回收过程

顺时针:Young GC → Young GC + Concurrent Mark → Mixed GC 顺序,进行垃圾回收

  • Young GC:发生在年轻代的 GC 算法,一般对象(除了巨型对象)都是在 eden region 中分配内存,当所有 eden region 被耗尽无法申请内存时,就会触发一次 Young GC,G1 停止应用程序的执行 STW,把活跃对象放入老年代,垃圾对象回收

    回收过程

    1. 扫描根:根引用连同 RSet 记录的外部引用作为扫描存活对象的入口

    2. 更新 RSet:处理 dirty card queue 更新 RS,此后 RSet 准确的反映对象的引用关系

      • dirty card queue:类似缓存,产生了引用先记录在这里,然后更新到 RSet

      • 作用:产生引用直接更新 RSet 需要线程同步开销很大,使用队列性能好

    3. 处理 RSet:识别被老年代对象指向的 Eden 中的对象,这些被指向的对象被认为是存活的对象,把需要回收的分区放入 Young CSet 中进行回收

    4. 复制对象:Eden 区内存段中存活的对象会被复制到 survivor 区,survivor 区内存段中存活的对象如果年龄未达阈值,年龄会加1,达到阀值会被会被复制到 old 区中空的内存分段,如果 survivor 空间不够,Eden 空间的部分数据会直接晋升到老年代空间

    5. 处理引用:处理 Soft,Weak,Phantom,JNI Weak 等引用,最终 Eden 空间的数据为空,GC 停止工作

  • Concurrent Mark

    • 初始标记:标记从根节点直接可达的对象,这个阶段是 STW 的,并且会触发一次年轻代 GC

    • 并发标记 (Concurrent Marking):在整个堆中进行并发标记(应用程序并发执行),可能被 YoungGC 中断。会计算每个区域的对象活性,即区域中存活对象的比例,若区域中的所有对象都是垃圾,则这个区域会被立即回收(实时回收),给浮动垃圾准备出更多的空间,把需要收集的 Region 放入 CSet 当中

    • 最终标记:为了修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录,虚拟机将这段时间对象变化记录在线程的 Remembered Set Logs 里面,最终标记阶段需要把 Remembered Set Logs 的数据合并到 Remembered Set 中,这阶段需要停顿线程,但是可并行执行(防止漏标

    • 筛选回收:并发清理阶段,首先对 CSet 中各个 Region 中的回收价值和成本进行排序,根据用户所期望的 GC 停顿时间来制定回收计划,也需要 STW

  • Mixed GC:当很多对象晋升到老年代时,为了避免堆内存被耗尽,虚拟机会触发一个混合的垃圾收集器,即 Mixed GC,除了回收整个 young region,还会回收一部分的 old region,过程同 YGC

    注意:是一部分老年代,而不是全部老年代,可以选择哪些老年代 region 收集,对垃圾回收的时间进行控制

    在 G1 中,Mixed GC 可以通过 -XX:InitiatingHeapOccupancyPercent 设置阈值

  • Full GC:对象内存分配速度过快,Mixed GC 来不及回收,导致老年代被填满,就会触发一次 Full GC,G1 的 Full GC 算法就是单线程执行的垃圾回收,会导致异常长时间的暂停时间,需要进行不断的调优,尽可能的避免 Full GC

    产生 Full GC 的原因:

    • 晋升时没有足够的空间存放晋升的对象

    • 并发处理过程完成之前空间耗尽,浮动垃圾


相关参数
  • -XX:+UseG1GC:手动指定使用 G1 垃圾收集器执行内存回收任务

  • -XX:G1HeapRegionSize:设置每个 Region 的大小。值是 2 的幂,范围是 1MB 到 32MB 之间,目标是根据最小的 Java 堆大小划分出约 2048 个区域,默认是堆内存的 1/2000

  • -XX:MaxGCPauseMillis:设置期望达到的最大 GC 停顿时间指标,JVM会尽力实现,但不保证达到,默认值是 200ms

  • -XX:+ParallelGcThread:设置 STW 时 GC 线程数的值,最多设置为 8

  • -XX:ConcGCThreads:设置并发标记线程数,设置为并行垃圾回收线程数 ParallelGcThreads 的1/4左右

  • -XX:InitiatingHeapoccupancyPercent:设置触发并发 Mixed GC 周期的 Java 堆占用率阈值,超过此值,就触发 GC,默认值是 45

  • -XX:+ClassUnloadingWithConcurrentMark:并发标记类卸载,默认启用,所有对象都经过并发标记后,就可以知道哪些类不再被使用,当一个类加载器的所有类都不再使用,则卸载它所加载的所有类

  • -XX:G1NewSizePercent:新生代占用整个堆内存的最小百分比(默认5%)

  • -XX:G1MaxNewSizePercent:新生代占用整个堆内存的最大百分比(默认60%)

  • -XX:G1ReservePercent=10:保留内存区域,防止 to space(Survivor中的 to 区)溢出


调优

G1 的设计原则就是简化 JVM 性能调优,只需要简单的三步即可完成调优:

  1. 开启 G1 垃圾收集器

  2. 设置堆的最大内存

  3. 设置最大的停顿时间(STW)

不断调优暂停时间指标:

  • XX:MaxGCPauseMillis=x 可以设置启动应用程序暂停的时间,G1会根据这个参数选择 CSet 来满足响应时间的设置

  • 设置到 100ms 或者 200ms 都可以(不同情况下会不一样),但设置成50ms就不太合理

  • 暂停时间设置的太短,就会导致出现 G1 跟不上垃圾产生的速度,最终退化成 Full GC

  • 对这个参数的调优是一个持续的过程,逐步调整到最佳状态

不要设置新生代和老年代的大小:

  • 避免使用 -Xmn 或 -XX:NewRatio 等相关选项显式设置年轻代大小,G1 收集器在运行的时候会调整新生代和老年代的大小,从而达到我们为收集器设置的暂停时间目标

  • 设置了新生代大小相当于放弃了 G1 的自动调优,我们只需要设置整个堆内存的大小,剩下的交给 G1 自己去分配各个代的大小



http://www.kler.cn/a/503569.html

相关文章:

  • 基于ESP8266 wifimanager实现WiFi配置及天气显示
  • HTTP详解——HTTP基础
  • MMDetection框架下的常见目标检测与分割模型综述与实践指南
  • 前端开发:表格、列表、表单
  • 【Elasticsearch7.11】postman批量导入少量数据
  • 泛目录和泛站有什么差别
  • C# 配置文件:app.config 和 web.config
  • Flask简介
  • Scala 异常处理
  • 代码随想录刷题day06|(数组篇)54.螺旋矩阵(补1.13
  • 简要认识JAVAWeb技术三剑客:HTMLCSSJavaScript
  • Android硬件通信之 USBManager通信
  • mybatis-spring @MapperScan走读分析
  • 国产编辑器EverEdit - 一个优秀的文本编辑器该有的删除功能
  • Chat2DB
  • Vue.js 组件开发:构建可复用的UI元素
  • 【深度学习】PyTorch:手写数字识别
  • 接口测试Day09-数据库工具类封装
  • nvm use使用nodejs版本时报错
  • 深度学习学习笔记(第29周)
  • 【Linux】【内存】Buddy内存分配基础 NUMA架构
  • HarmonyOS NEXT边学边玩,从零开发一款影视APP(二、首页轮播图懒加载的实现)
  • 用css 现实打字机效果
  • 嵌入式系统Linux实时化(四)Xenomai应用开发测试
  • Spring Boot 2 学习指南与资料分享
  • PHP优校管理系统