jvm垃圾回收器之G1

296 阅读4分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第10天,点击查看活动详情

(4)G1

image-20220331154338363

  • 定义: Garbage First

  • JDK 9以后默认使用,而且替代了CMS 收集器

  • 适用场景

    • 同时注重吞吐量和低延迟(响应时间)
    • 超大堆内存(内存大的),会将堆内存划分为多个大小相等的区域
    • 整体上是标记-整理算法,两个区域之间是复制算法
  • 相关参数,JDK8 并不是默认开启的,所需要参数开启

    image-20220331154506276

a. G1 垃圾回收阶段

image-20220331161535710

新生代伊甸园垃圾回收—–>内存不足,新生代回收+并发标记—–>回收新生代伊甸园、幸存区、老年代内存 ——>新生代伊甸园垃圾回收(重新开始)

b.Young Collection

分区算法region

分代是按对象的生命周期划分,分区则是将堆空间划分连续几个不同小区间,每一个小区间独立回收,可以控 制一次回收多少个小区间,方便控制 GC 产生的停顿时间

E:伊甸园 S:幸存区 O:老年代

会STW

image-20220331161638986

image-20220331161652431

image-20220331161706231

c.Young Collection + CM

CM:并发标记

  • 在 Young GC 时会对 GC Root 进行初始标记

  • 在老年代占用堆内存的比例达到阈值时,对进行并发标记(不会STW),阈值可以根据用户来进行设定

    image-20220331161751823

d.Mixed Collection

会对E S O 进行全面的回收

  • 最终标记
  • 拷贝存活

-XX:MaxGCPauseMills:xxx 用于指定最长的停顿时间

为什么有的老年代被拷贝了,有的没拷贝?

因为指定了最大停顿时间,如果对所有老年代都进行回收,耗时可能过高。为了保证时间不超过设定的停顿时 间,会回收最有价值的老年代(回收后,能够得到更多内存)

image-20220331161853706

e.Full GC

G1在老年代内存不足时(老年代所占内存超过阈值)

  • 如果垃圾产生速度慢于垃圾回收速度,不会触发Full GC,还是并发地进行清理
  • 如果垃圾产生速度快于垃圾回收速度,便会触发Full GC

f.Young Collection 跨代引用

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

image-20220331162021179

  • 卡表与Remembered Set

    • Remembered Set 存在于E中,用于保存新生代对象对应的脏卡
    • 脏卡:O被划分为多个区域(一个区域512K),如果该区域引用了新生代对象,则该区域被称 为脏卡
  • 在引用变更时通过post-write barried + dirty card queue

  • concurrent refinement threads 更新 Remembered Set

image-20220331162129796

g.Remark

重新标记阶段

在垃圾回收时,收集器处理对象的过程中

黑色:已被处理,需要保留的 灰色:正在处理中的 白色:还未处理的

image-20220331162157975

但是在并发标记过程中,有可能A被处理了以后未引用C,但该处理过程还未结束,在处理过程结束之前A引用 了C,这时就会用到remark

过程如下

  • 之前C未被引用,这时A引用了C,就会给C加一个写屏障,写屏障的指令会被执行,将C放入一个队列当 中,并将C变为 处理中 状态
  • 在并发标记阶段结束以后,重新标记阶段会STW,然后将放在该队列中的对象重新处理,发现有强引用 引用它,就会处理它

image-20220331162244377

h.JDK 8u20 字符串去重

过程

  • 将所有新分配的字符串(底层是char[])放入一个队列

  • 当新生代回收时,G1并发检查是否有重复的字符串

  • 如果字符串的值一样,就让他们引用同一个字符串对象

  • 注意,其与String.intern的区别

    • intern关注的是字符串对象
    • 字符串去重关注的是char[]
    • 在JVM内部,使用了不同的字符串标

优点与缺点

  • 节省了大量内存
  • 新生代回收时间略微增加,导致略微多占用CPU

i.JDK 8u40 并发标记类卸载

在并发标记阶段结束以后,就能知道哪些类不再被使用。如果一个类加载器的所有类都不在使用,则卸载它所 加载的所有类

j. JDK 8u60 回收巨型对象

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

image-20220331162526264

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

  • 并发标记必须在堆空间占满前完成,否则退化为 FullGC

  • JDK 9 之前需要使用 -XX:InitiatingHeapOccupancyPercent

  • JDK 9 可以动态调整

    • -XX:InitiatingHeapOccupancyPercent 用来设置初始值
    • 进行数据采样并动态调整
    • 总会添加一个安全的空档空间

l.JDK 9 更高效的回收