Java 垃圾回收器入门与演进:Serial、CMS、G1

一,JVM如何判断一个对象是“不可达”的

1,Stop The World(STW)

        GC开始前,暂停所有用户线程,防止引用结构在扫描过程中发生变化,避免“你扫我改”。

2,Root Scanning(根集合扫描)

JVM会收集所有被直接引用的对象作为 GC Roots(根对象),形成一个 Root Set,包括:

  • 所有线程栈帧中的局部变量(包括main线程和其他线程)
  • Metaspace中的静态字段引用
  • 运行时常量池中的对象引用
  • JNI本地方法中的对象引用
  • 类加载器自身持有的引用

3,可达性分析(Root Tracing)

        从这些 GC Roots 出发,沿着引用链递归遍历堆上的对象,对能访问的对象打上“可达”的标记。

4,扫描整个堆内存:

  • 没有被打上标记的对象即为不可达,就是垃圾对象,将成为GC的目标。

    二,GC算法

    1,Mark-Sweep(标记清除)
    • 第一遍扫描标记所有可达对象(活对象)
    • 第二遍扫描堆,清除没有被标记的

    缺点:内存碎片化,影响后续对象的连续分配效率。

    2,Copying(拷贝清除)
    • 将新生代内存被分成了Eden区,From Survivor区,To Survivor区,比例默认为8:1:1。
    • GC过程中,将活跃对象从Eden区,From Survivor区拷贝到To Survivor区。
    • 复制完成后,From Survivor区和To Survivor区身份逻辑互换,为下一次GC做准备。
    • 如果To Survivor区存活对象太多(大于容量一半),会触发动态年龄判断,提前将部分年龄较大的直接晋升为老年代。

    优点:只扫描一次,效率高,且没有碎片。

    缺点:移动复制对象,需要调整对象引用;始终保持一个Survivor区为空,浪费小部分空间。

    适用场景:关于Copying算法,更适用于新生代对象存活率低的场景,在GC完存活对象大于新生代总容量的5%(To Survivor的一半,就是5%),就会触发提前晋升。

    3,Mark-Compact(标记压缩)

    标记压缩是将存活的对象内存的一端移动,让所有活的对象紧凑地排在一起,然后将剩余的空间全部清除。

    • 第一次扫描,标记 + 计算目标位置
    • 第二次扫描,移动对象
    • 第三步,指针修正,更新所有引用指向新地址。

    优点:内存紧凑,无碎片

    缺点:效率慢,需要两次扫描+一次修正

    适用场景:老年代

    三,垃圾回收器组合

    1,Serial + Serial Old

    Java最早,最基础的垃圾回收器就是Serial + Serial Old组合。

    • GC类型:
      • 单线程 + STW
    • 算法:
      • Serial:新生代 - 复制算法
      • Serial Old:老年代 - 标记清除算法
    • 优点:
      • 简单可靠,实现简单
    • 缺点:
      • 单线程,效率低,GC停顿时间长
    • 适用场景:
      • 轻量级应用,小堆内存
    2,Parallel Scavenge + Parallel Old
    • GC类型:
      • 多线程 + STW
    • 算法:
      • PS:新生代 - 复制算法
      • PO:老年代 - 标记整理算法。
    • 优点:
      • 并行执行,充分利用多核CPU资源,GC速度快。
      • GC干净利落,次数相对少。
    • 缺点:
      • STW停顿时间长,不适合对响应时间要求高的场景
    • 适用场景:
      • 吞吐量优先的批处理或后台任务,对响应速度要求不高的场景。
    3,Parallel New + CMS
    • GC类型:
      • PN 多线程 + STW,CMS 大部分并发
    • 算法:
      • PN:新生代 - 复制算法
      • CMS:老年代 - 标记清除算法
    • 优点:
      • 响应速度快,大部分阶段和应用线程并发执行
    • 缺点:
      • 会产生浮动垃圾
      • 内存碎片化严重
      • 清理速度慢时会触发Full GC(严重卡顿)
    • 适用场景:
      • Web 等服务对响应时间要求高的系统
    PS + PO 与 PN + CMS 的对比
    特性PS + POPN + CMS
    新生代收集器Parallel ScavengeParNew
    老年代收集器Parallel OldCMS(Concurrent Mark Sweep)
    并发能力全 STW,多线程并行 GC大部分并发,仅部分 STW
    回收算法新生代复制 + 老年代整理新生代复制 + 老年代清除
    吞吐量高,适合批处理中等
    响应时间一次 GC 停顿较长停顿短,响应更好
    缺点STW 时间长容易产生碎片,浮动垃圾,可能触发 Full GC
    适用场景后台任务,吞吐量优先Web 服务,交互响应优先

    四,CMS

    CMS的四个阶段
    1. 初始标记(Initial Mark)
      1. STW
      2. 只标记 GC Roots 直接可达的对象,停顿极短
    2. 并发标记(Concurrent Mark)
      1. 与应用线程并发执行
      2. 从初始标记的对象出发递归扫描整个对象图,标记所有可达对象
      3. 用的是三色标记法,黑(标记完成),灰(待处理),白(未标记)
      4. 问题:并发标记期间引用结构可能被修改,产生“误判”
      5. 解决:利用写屏障 + 卡表机制记录并发期间对象引用变化(即增量更新)
    3. 重新标记(Remark)
      1. STW
      2. 对记录的引用变化对象进行“补标记”,确保不遗漏可达对象
    4. 并发清除(Concurrent Sweep)
      1. 与应用线程并发执行
      2. 清除未被标记的白色对象
      3. 这一阶段仍可能产生新的垃圾,称为浮动垃圾
    CMS的核心问题
    • 写屏障复杂,所有引用的写操作都要额外记录进卡表,影响应用线程性能。
    • 浮动垃圾,清理时仍有线程产生新垃圾,清理不彻底,只能等下GC。
    • Full GC退化严重,Full GC使用的是Serial Old,单线程标记清除,STW时间特别长。
    • 碎片问题,CMS是标记清除算法,不移动对象,会产生大量碎片。
    • 维护成本高,写屏障,卡表,增量更新逻辑复杂,代码难维护。

    CMS是第一个实现低延迟的老年代GC,解决了响应时间差的问题,但核心问题太多,最终被G1取代。

    五,G1

    G1 是第一个取消了物理分代的垃圾回收器,它的核心思想是:

            将整个堆划分为多个大小相同的Region,优先回收垃圾最多的 Region 。

    G1 支持 Young GC 和 Mixed GC,但不再固定 Eden,Survivor,Old 区的位置,而是通过 Region 标签动态管理。

    概念
    Collection Set (CSet)
    • 记录需要被回收的 Region 。Young GC 回收 Eden + Survivor,Mixed GC 加上 Old。
    • 生命周期:GC Trigger 之后立刻构建,GC 完成后释放。
    • 粒度:多个 Region
    Remembered Set (RSet)
    • 记录了其他Region中的对象到本Region的引用 。
    • 生命周期:程序运行期间一直维护。
    • 粒度:每个 Region
    Card Table
    • 记录了JVM中哪些内存区域发生了 “写引用” 操作。
    • 生命周期:程序运行期间一直维护。
    • 粒度:全局的 BitMap 表
    SATB Buffer
    • 在并发阶段,写屏障会将丢失引用指向的对象加到SATB Buffer里,在重新标记时扫描SATB Buffer,将里面的对象标为活对象。
    • 生命周期:并发标记期间启用。
    • 粒度:每个线程持有
    G1的四个阶段
    初始标记
    1. STW
    2. 标记 GC Roots
    3. 开启 SATB 模式:
    并发标记
    1. 与应用线程并发执行
    2. 从GC Roots 开始遍历,三色标记
    3. 可能的问题:并发期间对象结构会变(引用断了或换了),导致对象“误删”
    4. 解决方案:写屏障记录两件事情,(假如 a.ref = b 变成 a.ref = c)
      1. 被断开的旧引用对象加入SATB Buffer,(将 b 加入 SATB Buffer)。
      2. 修改的字段所在 Card 标记为 Dirty。(将 a 所在的 Card 标为 Dirty)。
    重新标记 
    1. 再次 STW
    2. 扫描SATB Buffer,将快照时是活的对象,补救回来。(比如刚才的 b 对象)
    3. 扫描Dirty Card,更新RemSet,防止遗漏引用
    回收
    1. CSet 中活着的对象 复制到其他 Region。
    2. 被搬空的 Region,标记为 Free Region,等待重新分配。
    3. 整个 Region 粒度的回收,不再逐个清理对象。

    G1 不是传统意义上的 标记 + 清除,而是 标记 + 复制 + 按 Region 回收

    评论
    成就一亿技术人!
    拼手气红包6.0元
    还能输入1000个字符
     
    红包 添加红包
    表情包 插入表情
     条评论被折叠 查看
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值