Tavio's blog Tavio's blog
首页
  • JVM底层原理
  • 邪恶多线程
  • MyBatis底层原理
  • Spring底层原理
  • MySQL的优化之路
  • ClickHouse的高性能
  • Redis的快速查询
  • RabbitMQ的生产
  • Kafka的高吞吐量
  • ES的入门到入坑
  • MySQL自增ID主键空洞
  • 前端实现长整型排序
  • MySQL无感换表
  • Redis延时双删
  • 高并发秒杀优惠卷
  • AOP无侵入式告警
  • 长短链接跳转
  • 订单超时取消
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Tavio Zhang

努力学习的小码喽
首页
  • JVM底层原理
  • 邪恶多线程
  • MyBatis底层原理
  • Spring底层原理
  • MySQL的优化之路
  • ClickHouse的高性能
  • Redis的快速查询
  • RabbitMQ的生产
  • Kafka的高吞吐量
  • ES的入门到入坑
  • MySQL自增ID主键空洞
  • 前端实现长整型排序
  • MySQL无感换表
  • Redis延时双删
  • 高并发秒杀优惠卷
  • AOP无侵入式告警
  • 长短链接跳转
  • 订单超时取消
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • JVM整体架构
  • JVM String Table
  • JVM GC
    • 一、GC 的核心目标与挑战
    • 二、对象存活判定:如何识别"垃圾"
      • 2.1 引用计数法(已淘汰)
      • 2.2 可达性分析(JVM 主流算法)
    • 三、引用类型:影响对象的回收时机
    • 四、垃圾收集算法:如何回收垃圾
      • 4.1 复制算法:高效无碎片(空间换时间)
      • 4.2 标记-清除算法:无空间浪费(有碎片)
      • 4.3 标记-整理算法:无碎片(时间换空间)
    • 五、分代收集:结合算法优势的实战模型
      • 5.1 内存区域划分
      • 5.2 新生代细节
      • 5.3 老年代细节
    • 六、垃圾回收器:算法的工程实现
      • 6.1 Serial 回收器(串行回收)
      • 6.2 Parallel 回收器(并行回收)
      • 6.3 CMS 回收器(并发标记清除,已废弃)
      • 6.4 G1 回收器(区域化分代式,JDK9+ 默认)
      • 6.5 ZGC 回收器(低延迟,JDK17+ 主流)
    • 七、总结:如何选择合适的 GC 策略
  • JVM 对象的创建与调用
  • 《JVM》笔记
Tavio
2022-02-23
目录

JVM GC

# JVM 垃圾回收(GC)深度解析:从原理到实践

在 Java 虚拟机(JVM)的内存管理中,垃圾回收(Garbage Collection,简称 GC)是自动内存管理的核心机制。它负责识别并回收不再被使用的对象,释放内存空间,避免内存泄漏和溢出(OOM)。

# 一、GC 的核心目标与挑战

垃圾回收的核心目标是高效释放不再被使用的内存,但需平衡三个关键指标:

  • 准确性:不误删仍在使用的对象(避免空指针异常),不遗漏垃圾对象(避免内存浪费)。
  • 效率:回收过程对应用线程的影响(STW 时间)尽可能小。
  • 适应性:能适配不同场景(如高吞吐量、低延迟、大堆内存等)。

实现这一目标的核心流程可概括为:如何判定对象已死 → 如何高效回收垃圾 → 如何根据场景选择回收工具。

# 二、对象存活判定:如何识别"垃圾"

要回收垃圾,首先需明确"什么是垃圾"——即不再被任何途径访问的对象。JVM 主要通过两种算法判定对象存活状态:

# 2.1 引用计数法(已淘汰)

  • 原理:为每个对象维护一个引用计数器,当对象被引用时计数器+1,引用失效时-1;当计数器为 0 时,判定为垃圾。
  • 优势:实现简单,判定效率高。
  • 缺陷:无法解决循环引用问题(如 A 引用 B,B 引用 A,两者计数器始终为 1,永远无法被回收)。
  • 现状:因循环引用缺陷,JVM 未采用该算法。

# 2.2 可达性分析(JVM 主流算法)

  • 原理:以"GC Root"为起点,通过遍历对象引用链(引用关系),若对象无法从任何 GC Root 可达,则标记为垃圾。
  • GC Root 范围(核心起点):
    • 虚拟机栈(栈帧局部变量表)中引用的对象(如方法参数、局部变量);
    • 方法区中类的静态变量引用的对象;
    • 本地方法栈中 Native 方法引用的对象;
    • 活跃线程对象(如正在运行的线程实例);
    • 被同步锁(synchronized)持有的对象。
  • 优势:解决了循环引用问题,是 JVM 判定垃圾的标准算法。

# 三、引用类型:影响对象的回收时机

对象的"存活"不仅取决于是否可达,还与引用类型相关。JVM 定义了 4 种引用类型,从强到弱影响对象的回收优先级:

引用类型 定义与特性 回收时机 典型场景
强引用 最常见的引用(如 User user = new User()),默认引用类型。 仅当引用链完全断开(如 user = null)才会被回收。 普通对象存储(如业务对象)。
软引用 用 SoftReference 包装(如 SoftReference<User> softRef = new SoftReference<>(user))。 内存不足时(OOM 前)主动回收。 缓存(如图片缓存,内存充足时保留)。
弱引用 用 WeakReference 包装(如 WeakReference<User> weakRef = new WeakReference<>(user))。 下次 GC 时必然回收(无论内存是否充足)。 临时缓存(如与对象关联的辅助信息)。
虚引用 用 PhantomReference 包装,必须结合引用队列(ReferenceQueue)使用。 回收时触发队列通知,对象本身直接被回收。 跟踪对象回收时机(如释放堆外内存)。

# 四、垃圾收集算法:如何回收垃圾

确定垃圾后,需通过具体算法回收内存。JVM 设计了三种基础算法,各有优劣,适用于不同场景:

# 4.1 复制算法:高效无碎片(空间换时间)

  • 原理:将内存划分为大小相等的两块(From 区和 To 区),仅使用 From 区分配对象。GC 时:
    1. 标记 From 区中所有存活对象;
    2. 将存活对象完整复制到 To 区(按顺序排列,消除碎片);
    3. 清空 From 区,交换 From/To 角色(下次使用原 To 区)。
  • 优势:
    • 回收效率高(仅处理存活对象,无需扫描垃圾);
    • 无内存碎片(复制后对象连续排列)。
  • 缺陷:
    • 空间利用率低(仅 50% 内存可用);
    • 存活对象越多,复制成本越高。
  • 适用场景:新生代(对象存活率低,复制成本低)。

# 4.2 标记-清除算法:无空间浪费(有碎片)

  • 原理:分两个阶段:
    1. 标记:从 GC Root 出发,标记所有存活对象;
    2. 清除:扫描整个内存区域,回收所有未标记的垃圾对象。
  • 优势:
    • 空间利用率高(无需额外空闲区);
    • 适合大对象(无需复制,处理成本低)。
  • 缺陷:
    • 产生内存碎片(回收后空闲内存分散,可能无法分配大对象);
    • 效率低(需遍历两次内存:标记一次,清除一次)。
  • 适用场景:存活对象少、垃圾多的场景(较少单独使用,多作为基础算法优化)。

# 4.3 标记-整理算法:无碎片(时间换空间)

  • 原理:标记-清除算法的优化版,分三个阶段:
    1. 标记:同标记-清除(标记存活对象);
    2. 整理:将所有存活对象向内存一端移动并按顺序排列;
    3. 清除:回收内存另一端的所有垃圾对象。
  • 优势:
    • 无内存碎片(解决标记-清除的核心缺陷);
    • 空间利用率高。
  • 缺陷:
    • 效率更低(增加了对象移动成本)。
  • 适用场景:老年代(对象存活率高,需避免碎片以分配大对象)。

# 五、分代收集:结合算法优势的实战模型

由于对象生命周期差异显著(大部分对象"朝生夕死",少数对象长期存活),JVM 采用分代收集模型,结合上述算法优势:

# 5.1 内存区域划分

JVM 堆内存分为新生代(Young Generation)和老年代(Old Generation),比例通常为 1:2(可通过参数调整)。

区域 存储对象类型 特点 收集算法 收集触发时机
新生代 新创建的对象(朝生夕死) 对象存活率低(约 5%) 复制算法 Minor GC(轻量 GC)
老年代 长期存活的对象(存活久) 对象存活率高(约 95%) 标记-整理算法 Major GC/Full GC

# 5.2 新生代细节

新生代进一步分为:

  • Eden 区(伊甸园):新对象优先分配的区域(占新生代 80%);
  • 两个 Survivor 区(From 区和 To 区,各占 10%):用于存放 Minor GC 后存活的对象。

Minor GC 流程:

  1. 当 Eden 区满时,触发 Minor GC;
  2. 标记 Eden 区 + From 区的存活对象;
  3. 将存活对象复制到 To 区,同时对象年龄+1(年龄记录在对象头中);
  4. 清空 Eden 区和 From 区,交换 From/To 角色;
  5. 当对象年龄达到阈值(默认 15,可通过 -XX:MaxTenuringThreshold 调整),晋升至老年代。

# 5.3 老年代细节

老年代存储长期存活的对象,触发回收的情况包括:

  • 老年代内存不足;
  • 新生代对象晋升失败(如大对象直接进入老年代,或 Survivor 区对象放不下);
  • 显式调用 System.gc()(不推荐,可能触发 Full GC)。

Major GC/Full GC:

  • Major GC:仅回收老年代(较少单独触发);
  • Full GC:同时回收新生代和老年代(STW 时间长,应尽量避免)。

# 六、垃圾回收器:算法的工程实现

回收器是垃圾收集算法的具体实现,不同回收器针对不同场景(吞吐量、延迟等)优化。主流回收器如下:

# 6.1 Serial 回收器(串行回收)

  • 特点:单线程执行 GC,全程触发 STW(Stop The World,暂停所有用户线程)。
  • 算法实现:
    • 新生代:复制算法;
    • 老年代:标记-整理算法。
  • 适用场景:单 CPU、小内存(如嵌入式设备)、低并发场景(STW 时间长,不适合高并发)。
  • 参数配置:-XX:+UseSerialGC(启用 Serial + Serial Old 组合)。

# 6.2 Parallel 回收器(并行回收)

  • 特点:多线程并行执行 GC,以高吞吐量为目标(吞吐量 = 业务时间 / (业务时间 + GC 时间))。
  • 算法实现:
    • 新生代:并行复制算法;
    • 老年代:并行标记-整理算法。
  • 优势:STW 时间比 Serial 短(多线程加速)。
  • 适用场景:多 CPU、高吞吐量需求(如后台计算)、对延迟不敏感的场景。
  • 参数配置:-XX:+UseParallelGC(JDK8 默认,新生代 Parallel Scavenge + 老年代 Parallel Old)。

# 6.3 CMS 回收器(并发标记清除,已废弃)

  • 特点:以低延迟为目标,老年代回收的大部分阶段与用户线程并发执行(减少 STW 时间)。
  • 算法实现:老年代采用标记-清除算法(新生代默认搭配 Parallel 回收器)。
  • 回收流程:
    1. 初始标记:标记 GC Root 直接引用的对象(STW,时间短);
    2. 并发标记:从初始标记对象出发,遍历所有可达对象(与用户线程并行,无 STW);
    3. 重新标记:修正并发标记中因用户线程修改引用导致的偏差(STW,时间短);
    4. 并发清除:回收未标记的垃圾对象(与用户线程并行,无 STW)。
  • 缺陷:
    • 产生内存碎片(标记-清除算法导致);
    • 对 CPU 资源敏感(并发阶段占用 CPU);
    • JDK9 标记废弃,JDK14 移除。
  • 参数配置:-XX:+UseConcMarkSweepGC(需搭配 -XX:+UseParNewGC 启用新生代并行回收)。

# 6.4 G1 回收器(区域化分代式,JDK9+ 默认)

  • 特点:融合分代回收与分区回收,兼顾吞吐量与低延迟,支持大堆内存(数十 GB)。

  • 内存模型:G1 摒弃了 CMS/Parallel Old 的 “连续新生代、连续老年代” 布局,将堆划分为多个大小相等的独立 Region,核心细节如下:

    1. Region 大小计算:
      • Region 大小由 JVM 在启动时自动计算,范围 1MB~32MB(2 的幂次),公式:Region大小 = 堆总大小 / Region数量(默认 Region 数量约 2048 个);
      • 可通过参数-XX:G1HeapRegionSize手动指定(需为 2 的幂次,如 1M/2M/4M,不建议随意修改)。
    2. Region 类型(动态切换):
    类型 作用 特殊说明
    Eden Region 新生代,存放新创建的对象 多个 Eden Region 可并行回收,占堆比动态调整(无需固定比例,如 ParNew 的 8:1:1)
    Survivor Region 新生代,存放 Eden 回收后存活的对象 同样动态分配数量,默认分为 S0/S1,但 Region 数量不固定
    Old Region 老年代,存放存活时间长的对象 由 Survivor 晋升而来,或大对象拆分后存入
    Humongous Region 存放大对象(大小≥Region 一半),占用连续多个 Region 直接划入老年代范畴,回收时需整体处理,避免跨 Region 碎片
    1. Remembered Set(记忆集,G1 核心优化):
      • 问题:Region 是独立的,跨 Region 引用(如 Old Region 引用 Eden Region 对象)会导致 GC 时需要全堆扫描;
      • 解决方案:每个 Region 维护一个 Remembered Set,记录 “外部 Region 对本 Region 对象的引用”;
      • 实现:通过写屏障(Write Barrier)拦截对象引用更新,异步更新 Remembered Set,避免全堆扫描,大幅降低 GC 耗时。
  • 核心优势:

    • 动态调整:无需固定新老年代比例,根据负载灵活分配 Region 角色;
    • 优先回收:每次 GC 优先选择垃圾占比最高的 Region(最大化回收效率);
    • 无碎片:采用复制算法(存活对象复制到新 Region)。
  • 回收流程:

    • 新生代 GC:
      1. 暂停所有用户线程(STW);
      2. 采用复制算法:将 Eden + Survivor 中存活的对象复制到新的 Survivor Region(或晋升到 Old Region);
      3. 清空原 Eden 和 Survivor Region,标记为空闲;
      4. 恢复用户线程。
    • 混合 GC(同时回收新老年代):
      1. 初始标记(STW,毫秒级):
      • 标记 GC Root 直接引用的对象;
      • 借助 Young GC 的 STW 阶段完成,无需额外暂停(优化点)。
      1. 并发标记(无 STW):
      • 从 GC Root 出发,遍历全堆对象图,标记所有存活对象;
      • 同时用户线程正常运行,通过写屏障处理 “并发标记期间的对象引用变化”(增量更新)。
      1. 最终标记(STW,毫秒级):
      • 修正并发标记期间因用户线程操作导致的标记偏差;
      • 处理 Remembered Set 中的跨 Region 引用,标记遗漏的存活对象。
      1. 筛选回收(STW,核心优化):
      • 统计所有 Region 的垃圾占比(存活对象比例);
      • 按 “垃圾占比从高到低” 排序,优先回收垃圾最多的 Region(贪心算法,最大化回收效率);
      • 控制本次回收的 Region 数量,确保 STW 时间不超过-XX:MaxGCPauseMillis(默认 200ms);
      • 采用复制算法:将存活对象复制到空闲 Region,清空原 Region。
  • 参数配置:-XX:+UseG1GC(JDK9+ 默认)。

# 6.5 ZGC 回收器(低延迟,JDK17+ 主流)

  • 特点:专为低延迟、大堆内存(TB 级)设计,STW 时间控制在毫秒级以内。

  • 内存模型:ZGC 继承了 G1 的 “Region 分区” 思想,但做了颠覆性优化 —— 抛弃 G1 固定大小 Region + 静态分配的设计,改为分级 Region + 动态管理,完美适配不同大小对象与弹性内存需求。

    1. Region 分级设计: ZGC 将 Region 分为三类固定大小的分区,避免 G1 中 “大对象占用连续 Region” 的问题,每个 Region 仅存储对应尺寸的对象:
    Region 类型 固定大小 适用对象 核心特点
    Small 2MB 小对象(<2MB) 占堆内存 90% 以上,回收效率最高
    Medium 32MB 中等对象(2MB~32MB) 避免小 Region 拆分中等对象,减少内存浪费
    Large N×2MB 大对象(>32MB) 每个 Large Region 仅存一个大对象,大小为 2MB 整数倍,回收时整体处理
    1. 动态 Region 管理: 与 G1 启动时一次性创建所有 Region 不同,ZGC 的 Region 具备 “动态弹性”:
      • 按需创建:堆内存不足时自动新建 Region,无需启动时预留大量内存;
      • 空闲销毁::回收后的空闲 Region 可销毁,释放物理内存(适配容器 / 云环境的内存弹性伸缩);
      • 无固定比例:无需像 G1 那样限制新生代 / 老年代占比,完全根据对象分配需求动态调整。
    2. 抛弃 Remembered Set(RS):G1 为解决跨 Region 引用问题,引入 RS(记忆集)导致额外内存开销(堆的 10%~20%);而 ZGC 通过 “颜色指针” 技术直接解决跨 Region 引用问题,彻底抛弃 RS,额外内存开销仅为堆的 1%~5%,大幅降低资源消耗。
  • 核心优化:ZGC 的灵魂:颜色指针技术

    颜色指针是 ZGC 实现 “极致低延迟” 的核心,也是区别于所有传统 GC 的关键 —— 它颠覆了在对象头标记 GC 状态的传统思路,转而通过指针本身标记对象状态。

    1. 颜色指针的底层原理:

    颜色指针的实现依赖硬件架构特性:AMD64(x86_64)架构下,64 位指针仅使用低 48 位(可寻址 256TB 内存),剩余 16 位为 “空闲位”。ZGC 复用这些空闲位作为 “标记位”,直接通过指针标记对象的 GC 状态,无需修改对象头。

    简化的指针结构如下:

    63~48位:保留位(未使用)
    47~0位: 内存地址(实际寻址)
    └─ 47~44位:颜色标记位(存储对象GC状态)
    
    1
    2
    3
    1. 颜色指针的核心状态:

    ZGC 定义了三类核心 “颜色”(状态),通过标记位实现对象状态管理,无需遍历对象:

    颜色 状态含义 核心作用
    白色 对象未被标记,属于垃圾 回收阶段直接清理
    灰色 对象已标记,但子对象未遍历 并发标记阶段的临时状态,需继续遍历子对象
    黑色 对象及所有子对象均已标记 存活对象,回收阶段保留
    转发色(扩展) 对象已复制到新 Region 重分配阶段临时状态,指引用户线程访问新对象地址
    1. 颜色指针的核心优势:
      • 无全局扫描:通过指针直接判断对象状态,无需遍历全堆 / Region,大幅减少 GC 耗时;
      • 轻量级并发:用户线程访问对象时,CPU 的内存保护机制(MMU)自动重定向到最新对象地址,无需复杂写屏障;
      • STW 极短:所有状态标记在指针层面完成,仅初始 / 最终标记需极短 STW,无修改对象头的额外开销。
  • 回收流程:

    1. 初始标记(STW,微秒级):
      • 核心操作:标记 GC Root 直接引用的对象(如虚拟机栈、本地方法栈引用);
      • 耗时特点:仅与 GC Root 数量相关,与堆大小无关,通常 < 1ms。
    2. 并发标记(无 STW):
      • 核心操作:从 GC Root 出发,遍历对象图,通过颜色指针标记所有存活对象;
      • 关键优化:用户线程正常运行,ZGC 通过 “读屏障” 处理并发标记期间的对象引用变化,无性能瓶颈。
    3. 并发预备重分配:
      • 核心操作:统计所有 Region 的垃圾占比,按 “垃圾占比从高到低” 筛选待回收 Region(贪心策略);
      • 前置准备:为待回收 Region 分配新的空闲 Region,准备复制存活对象。
    4. 重分配标记(STW,微秒级):
      • 核心操作:修正并发标记期间因用户线程操作导致的指针引用偏差;
      • 关键目标:标记所有指向 “待回收 Region” 的 GC Root 引用,耗时同样 < 1ms。
    5. 并发重分配(核心阶段,无 STW):
      • 核心操作:将待回收 Region 中的存活对象复制到新 Region;
      • 透明转发:用户线程访问旧 Region 对象时,通过 “转发色指针” 自动重定向到新对象地址,无感知。
    6. 并发重映射(无 STW):
      • 核心操作:异步更新所有指向旧 Region 的指针,使其直接指向新对象地址;
      • 容错设计:即使该阶段未完成,读屏障也会兜底转发,不影响业务运行。

    核心结论:ZGC 的 STW 时间仅来自 “初始标记 + 重分配标记”,且总耗时不随堆大小增长 ——1GB 堆和 1TB 堆的 STW 时间几乎一致。

  • 适用场景:对延迟敏感的大型应用(如分布式服务、大数据处理)。

  • 参数配置:-XX:+UseZGC(JDK11 引入,JDK17 成为长期支持版本)。

# 七、总结:如何选择合适的 GC 策略

GC 调优的核心是匹配业务场景:

  • 高吞吐量优先:选择 Parallel 回收器(如后台批处理任务);
  • 低延迟优先:选择 G1 或 ZGC(如 Web 服务、实时交易系统);
  • 小内存/嵌入式:选择 Serial 回收器。
编辑 (opens new window)
#JVM GC
上次更新: 2026/01/21, 19:29:14
JVM String Table
JVM 对象的创建与调用

← JVM String Table JVM 对象的创建与调用→

最近更新
01
订单超时取消
01-21
02
双 Token 登录
01-21
03
长短链接跳转
01-21
更多文章>
Theme by Vdoing
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式