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

垃圾收集器与核心算法详解(上)

垃圾收集器ParNew&CMS与底层三色标记算法详解

一、垃圾收集算法:分代理论下的三大核心实现

分代收集理论是基础,核心逻辑是按对象存活周期将堆分为新生代(存活短)和老年代(存活长),针对不同年代选择效率最优的算法。

算法名称 核心逻辑 适用年代 优点 缺点
复制算法 内存分为大小相等两块,用满一块后复制存活对象到另一块,清理原块 新生代 无内存碎片,效率高 内存利用率低(仅用 50%)
标记 - 清除算法 1. 标记(存活 / 待回收对象);2. 清除待回收对象 老年代 无需移动对象,简单 1. 效率低(标记对象多);2. 产生大量碎片
标记 - 整理算法 1. 标记存活对象;2. 存活对象移至内存一端;3. 清理边界外内存 老年代 无内存碎片 需移动对象,效率低于标记 - 清除

二、垃圾收集器:不同场景的实现选择

收集器是算法的落地,需根据 “吞吐量优先” 或 “低停顿优先” 选择,无 “最优” 仅 “适配”。

1. 新生代收集器对比
收集器名称 线程数 核心特性 算法 关键参数 适用场景
Serial 单线程 STW(暂停所有用户线程),无线程交互开销,单线程效率高 复制算法 -XX:+UseSerialGC 单 CPU 环境、客户端应用
Parallel Scavenge 多线程 关注吞吐量(用户代码时间 / 总时间),可自动优化内存 复制算法 -XX:+UseParallelGC-XX:ParallelGCThreads=N(默认 = CPU 核数) 服务器环境、吞吐量优先(如后台计算)
ParNew 多线程 与 CMS 收集器兼容(唯一能配合 CMS 的新生代收集器) 复制算法 -XX:+UseParNewGC 服务器环境、低停顿优先(如 Web 应用)
2. 老年代收集器对比
收集器名称 线程数 核心特性 算法 关键参数 适用场景
Serial Old 单线程 STW,Serial/Parallel Scavenge 的后备收集器 标记 - 整理算法 -XX:+UseSerialOldGC 单 CPU 环境、CMS 失败后的后备
Parallel Old 多线程 关注吞吐量,Parallel Scavenge 的老年代版本 标记 - 整理算法 -XX:+UseParallelOldGC 服务器环境、吞吐量优先(如大数据计算)
CMS 并发(多线程) 目标最短停顿时间,首次实现 GC 线程与用户线程并发运行 标记 - 清除算法 -XX:+UseConcMarkSweepGC 服务器环境、低停顿优先(如电商订单系统)
3. CMS 收集器核心细节

image-20250926183517035

  • 四步执行流程(关键,决定低停顿特性):

    1. 初始标记:STW,标记 GC Roots 直接引用的对象,耗时极短(毫秒级)。
    2. 并发标记:无 STW,遍历对象图,耗时久但与用户线程并发。
    3. 重新标记:STW,修正并发标记的漏标(用增量更新),耗时短于并发标记。
    4. 并发清理:无 STW,清理未标记对象,与用户线程并发;新增对象标记为黑色(浮动垃圾)。
  • 三大缺点

    1. CPU 敏感:并发阶段占用 CPU 资源,影响用户线程响应。
    2. 浮动垃圾:并发清理阶段产生的垃圾,需下一轮 GC 回收。
    3. 内存碎片:标记 - 清除算法导致,可通过-XX:+UseCMSCompactAtFullCollection在 FullGC 后整理。
  • 核心参数

    参数名称 作用 默认值
    -XX:ConcGCThreads 设置并发 GC 线程数 约 CPU 核数 1/4
    -XX:CMSInitiatingOccupancyFraction 老年代使用率达该比例触发 FullGC 92(百分比)
    -XX:CMSFullGCsBeforeCompaction 多少次 FullGC 后执行碎片整理 0(每次都整理)
    -XX:+CMSScavengeBeforeRemark CMS GC 前触发 Minor GC,减少标记开销 未开启

三、底层关键技术:保障并发标记的正确性

image-20250926183629122

1. 三色标记算法:解决并发标记的 “多标” 与 “漏标”
  • 颜色定义
    • 黑色:对象已扫描,且所有引用已遍历,安全存活(不可直接指向白色对象)。
    • 灰色:对象已扫描,但部分引用未遍历。
    • 白色:对象未扫描,分析结束后仍为白色则可回收。
  • 核心问题
    1. 多标(浮动垃圾):并发标记中,GC Roots(如局部变量)销毁,但其引用的对象已标记为非垃圾,本轮无法回收,需下一轮处理(不影响正确性)。
    2. 漏标(严重 bug):并发标记中,对象引用变化导致本应存活的白色对象未被标记,需通过读写屏障解决。
2. 漏标解决方案:增量更新与 SATB
方案名称 核心逻辑 适用收集器 实现方式
增量更新 黑色对象新增指向白色对象的引用时,记录该引用;重新标记阶段以黑色对象为根重新扫描 CMS 写屏障(post_write_barrier)
SATB(原始快照) 灰色对象删除指向白色对象的引用时,记录该引用;重新标记阶段以灰色对象为根重新扫描 G1/Shenandoah 写屏障(pre_write_barrier)

原始快照:重新标记阶段,没有新的对象引用之前记录的白色对象时,白色对象作为浮动垃圾,下一轮垃圾回收时再处理

  • 读写屏障:类似 AOP,在对象引用赋值 / 读取前后插入处理逻辑:
    • 写屏障:pre_write_barrier(赋值前记录旧引用)、post_write_barrier(赋值后记录新引用)。
    • 读屏障:pre_load_barrier(读取前记录引用),ZGC 使用。

这是C++代码级别的屏障,不同于内存屏障

3. 记忆集与卡表:优化跨代引用扫描
  • 问题背景:新生代 GC 时,若扫描老年代的跨代引用,效率极低。
  • 记忆集:记录 “非收集区(如老年代)指向收集区(如新生代)的引用”,避免全区域扫描。
  • 卡表:记忆集的实现方式(类似 HashMap 与 Map 的关系):
    • 结构:字节数组CARD_TABLE[],每个元素对应1 个卡页(HotSpot 中 1 卡页 = 512 字节)。
    • 逻辑:卡页内有跨代引用时,对应卡表元素设为 1(脏标记);GC 时仅扫描 “脏卡页” 对应的引用。
    • 维护:通过写屏障,在对象引用赋值时更新卡表的脏标记。

四、实战:亿级流量电商订单系统的 JVM 参数优化(ParNew+CMS)

1. 系统场景
  • 规模:日活 500 万,日均 50 万单,大促峰值 300 单 / 秒,每秒产生 60MB 对象(放大后)。
  • 目标:减少 FullGC,降低停顿时间。
2. 核心参数配置
参数类别 参数名称 取值 作用
内存基础 -Xms/-Xmx 3072M 堆内存固定,避免扩容开销
内存基础 -Xmn 2048M 新生代占比 2/3,减少对象进入老年代
内存基础 -Xss 1M 线程栈大小,避免栈溢出
元空间 -XX:MetaspaceSize/-XX:MaxMetaspaceSize 256M 元空间固定,避免频繁 GC
新生代优化 -XX:SurvivorRatio 8 Eden:Survivor=8:1:1,符合默认比例
新生代优化 -XX:MaxTenuringThreshold 5 对象 5 次 MinorGC 后进入老年代(默认 15)
新生代优化 -XX:PretenureSizeThreshold 1M >1M 的大对象直接进入老年代
CMS 优化 -XX:+UseParNewGC/-XX:+UseConcMarkSweepGC - 启用 ParNew+CMS 组合
CMS 优化 -XX:CMSInitiatingOccupancyFraction 92 老年代使用率 92% 触发 FullGC
CMS 优化 -XX:+UseCMSCompactAtFullCollection - FullGC 后执行碎片整理
CMS 优化 -XX:CMSFullGCsBeforeCompaction 3 每 3 次 FullGC 后整理一次(减少整理频率)

关键问题

问题 1:CMS 收集器以 “低停顿” 为核心优势,但其并发标记阶段可能出现 “漏标” 问题,导致本应存活的对象被误回收,JVM 是如何通过技术手段解决这一问题的?

答案:CMS 通过 “增量更新”+“写屏障” 解决漏标问题,具体逻辑如下:

  1. 漏标场景:并发标记中,黑色对象(已完成扫描)新增指向白色对象(未扫描)的引用,若不处理,白色对象会被误判为垃圾。
  2. 核心方案(增量更新)
    • 当黑色对象插入新的指向白色对象的引用时,通过写屏障的 post_write_barrier操作,将该新引用记录到 “重新标记集合(remark_set)” 中。
    • 并发标记结束后,进入 “重新标记” 阶段(短暂 STW),以记录集中的黑色对象为根,重新扫描其引用的白色对象,将这些白色对象标记为灰色 / 黑色,避免漏标。
  3. 写屏障的作用:类似 AOP 切面,在对象引用赋值(如a.d = d)的 “赋值后” 插入记录逻辑,确保所有新增的跨颜色引用都被捕获,为重新标记提供依据。

问题 2:新生代 GC(如 ParNew)时,为何需要 “卡表” 这一数据结构?它的工作原理是什么?

答案:卡表的核心作用是优化跨代引用的扫描效率,避免新生代 GC 时全量扫描老年代;其工作原理基于 “记忆集” 思想,具体如下:

  1. 必要性:新生代 GC 的收集范围是新生代,但老年代中可能存在指向新生代的引用(跨代引用),若全量扫描老年代,会导致 GC 效率极低。卡表通过记录 “老年代→新生代” 的引用位置,仅扫描这些位置,减少扫描范围。
  2. 工作原理
    • 结构:卡表是字节数组CARD_TABLE[],每个元素对应 1 个 “卡页”(HotSpot 中 1 卡页 = 512 字节),卡页覆盖老年代的内存区域。
    • 脏标记:当老年代某卡页内的对象新增指向新生代的引用时,通过写屏障将卡表中对应元素设为 1(脏标记)。
    • GC 扫描:新生代 GC 时,仅筛选卡表中 “脏标记(1)” 对应的卡页,扫描其中的跨代引用,将这些引用作为 GC Roots 的补充,无需扫描整个老年代。

问题 3:在亿级流量电商订单系统中,为何选择 “ParNew+CMS” 的收集器组合?并说明核心参数(如 - Xmn、-XX:MaxTenuringThreshold)的优化逻辑是什么?

答案:选择 “ParNew+CMS” 及参数优化的核心逻辑是匹配系统 “低停顿、高并发” 的需求,具体如下:

  1. 收集器组合选择原因
    • 系统特性:电商订单系统对响应时间敏感(如大促峰值 300 单 / 秒),需避免长时间 STW;ParNew 是新生代多线程收集器,可配合 CMS(老年代并发收集器),实现 “新生代快速 MinorGC + 老年代低停顿 FullGC”,符合低停顿需求。
    • 对比其他组合:Parallel Scavenge+Parallel Old 关注吞吐量,STW 时间较长,不适合用户交互场景;Serial+Serial Old 单线程,无法应对高并发,故排除。
  2. 核心参数优化逻辑
    • -Xmn=2048M:新生代内存设为 2GB(堆总 3GB 的 2/3),增大 Eden 区容量,减少 MinorGC 频率(每秒 60MB 对象需 30 秒才占满 Eden)。
    • -XX:MaxTenuringThreshold=5:对象需经历 5 次 MinorGC 才进入老年代(默认 15 次)。系统中多数对象(如订单对象)几秒内变为垃圾,5 次 MinorGC(约 150 秒)可筛选出真正长期存活的对象(如 Spring Bean),避免短期对象频繁进入老年代导致 FullGC。
    • -XX:PretenureSizeThreshold=1M:>1M 的大对象(如大缓存 Map)直接进入老年代,避免大对象在新生代频繁复制(复制算法对大对象成本高),减少 MinorGC 开销。
http://www.hskmm.com/?act=detail&tid=18233

相关文章:

  • 在Debian系统上修改开源软件源代码制作patch - 教程
  • WSL2搭建wordpress遇到的一点问题
  • 需求的系统规划 3
  • 430亿美元押注英国,Salesforce 加码 AI 投资
  • C# 中 ref 和 out 的学习笔记
  • C# 序列化三种方式
  • 区别:Modbus RTU 和 Modbus TCP
  • 记录安装机器/深度学习环境(conda、CUDA、pytorch)时的一些问题
  • 详细介绍:大数据毕业设计选题推荐:基于Hadoop+Spark的全球能源消耗数据分析与可视化系统
  • 深入解析:自动化接口框架搭建分享-pytest
  • 手撕深度学习之CUDA并行规约算法(上篇):硬核揭秘200%性能提升的GPU优化之道,从硬件特性到算法实现的完整进阶指南
  • 实战需求分析
  • 【RabbitMQ】主题(Topics)与主题交换机(Topic Exchange)
  • Ubuntu上编译 Linux_RT 内核
  • vue3 + vite Cannot access ‘xxx‘ before initialization
  • 《“悬荡”于理想与现实之间:一份关于人机共生未来的思想实验评估》
  • 区别:RS-232、RS-422、RS-485
  • 解决字符串数组中大整数精度问题
  • playwright-mcp入门
  • 【征文计划】深度剖析 Rokid SLAM 算法:从传感器融合到空间重建的完整技术链路 - 实践
  • 国信DRS数据恢复中心成为东芝(TOSHIBA)存储硬盘的数据恢复合作服务商
  • 深入解析Windows注册表regf文件格式
  • 华米运动步数修改,每天自动修改并同步 微信运动/支付宝运动 步数
  • IMU-坐标系-位姿
  • 今天做什么
  • 登录 Linux 自动展示 CPU/内存/磁盘挂载使用情况等信息(针对于银河麒麟调整的)
  • 解码数据结构线性表之链表
  • C++ placement new
  • Spring Boot接入邮箱,完成邮箱验证码
  • HyperWorks许可与网络安全