JVM 垃圾回收机制完整详解
2026/7/17 0:24:11 网站建设 项目流程

一、核心前置概念

1. 什么是 GC(Garbage Collection)

GC = 垃圾回收,指 JVM 自动识别不再被引用的对象,释放堆内存,避免手动管理内存(区别 C/C++ 手动 malloc/free)。 GC 核心解决两个问题:

  1. 怎么判断对象是垃圾(存活判定)
  2. 怎么回收垃圾(回收算法、收集器、内存分代)

2. JVM 内存分区与 GC 范围

JVM 运行时数据区分为 5 块,只有堆、元空间(永久代)存在 GC

  1. 程序计数器:无 GC,线程私有,生命周期随线程;
  2. 虚拟机栈 / 本地方法栈:栈帧自动出栈释放,不依赖 GC;
  3. 元空间(Metaspace,JDK8+):存放类、常量、静态变量,回收条件苛刻;
  4. 堆 HeapGC 主战场,所有 new 创建的对象存放于此,分代管理。

3. 堆内存分代模型(主流分代收集思想)

绝大多数收集器将堆划分为两大块,依据对象存活时间差异化回收:

  1. 新生代 Young Gen:存放短期存活对象,占堆 1/3
    • Eden 区:80% 新生代,绝大多数对象诞生于此
    • Survivor0 / Survivor1(S0、S1,幸存区):各 10% 新生代,永远一块空、一块存存活对象
  2. 老年代 Old Gen:存放长期存活对象,占堆 2/3 对象晋升规则:新生代对象熬过多次 Minor GC(默认 15 次)进入老年代;大对象直接进老年代。
GC 分类
  • Minor GC(年轻代 GC):只回收新生代,频率高、速度快,触发后 STW 很短
  • Major GC:仅回收老年代,很少单独触发
  • Full GC:整堆回收(新生代 + 老年代 + 元空间),STW 极长,业务需极力避免

二、对象存活判定:两种垃圾标记算法

GC 第一步:标记哪些对象是垃圾,核心难点是循环引用

1. 引用计数法

逻辑:每个对象维护计数器,引用 + 1,引用失效 - 1;计数 = 0 即为垃圾。 缺陷:无法解决对象循环引用(A 引用 B,B 引用 A,计数器永远不为 0),因此 JVM 弃用。

2. 可达性分析算法

核心逻辑

GC Roots(根对象)向下遍历,能遍历到的对象判定为存活,不可达为垃圾。

GC Roots 包含哪些对象
  1. 虚拟机栈中局部变量引用的对象
  2. 本地方法栈 JNI 引用的对象
  3. 静态变量、常量引用的对象(元空间)
  4. 运行中线程、锁对象、Class 对象等
四种引用
  1. 强引用Object o = new Object();GC 永不回收,内存溢出根源
  2. 软引用 SoftReference:内存不足时才回收,适合缓存
  3. 弱引用 WeakReference:下次 GC 必回收,ThreadLocalMap 底层使用
  4. 虚引用 PhantomReference:仅做回收通知,无法获取对象,用于堆外内存释放

三、三大垃圾回收基础算法

标记完成后,使用以下算法清理内存,收集器都是组合使用。

1. 标记 - 清除 Mark-Sweep

流程:1. 标记垃圾 → 2. 直接清除垃圾 优点:逻辑简单,无需移动对象 缺点:内存产生大量内存碎片;分配大对象时容易触发 Full GC

2. 复制 Copying

流程:将存活对象复制到空白内存区,清空原区域 新生代 Eden+S0/S1 就是此算法: Eden 满了,存活对象复制到空 Survivor,清空 Eden;下次 GC 交换两块 Survivor。 优点:无内存碎片,分配内存只需指针碰撞,速度极快 缺点:需要双倍内存空间,不适合大对象(老年代不用)

3. 标记 - 整理 Mark-Compact

流程:1. 标记垃圾 → 2. 所有存活对象向内存一端移动,边界外全部清空 优点:无内存碎片,适合老年代长期对象 缺点:移动对象需要 STW,开销比标记清除大

算法总结

  • 新生代:复制算法(对象存活率低,复制成本小)
  • 老年代:标记清除 / 标记整理(对象存活率高,复制代价太大)

四、分代回收完整流程

  1. 新对象全部分配在 Eden 区;
  2. Eden 空间耗尽,触发 Minor GC;
  3. 通过可达性分析标记 Eden、使用中的 Survivor 存活对象;
  4. 将存活对象复制到空白 Survivor 区,对象年龄 + 1;
  5. Eden、原 Survivor 全部清空,交换两块 Survivor 角色;
  6. 对象年龄达到阈值(-XX:MaxTenuringThreshold=15),直接晋升老年代;
  7. 若 Survivor 放不下存活对象,存活对象直接晋升老年代(分配担保);
  8. Minor GC 前会检查老年代可用空间,若不足直接触发 Full GC。

五、STW(Stop-The-World)关键概念

  1. 含义:GC 标记、复制、整理阶段,所有用户业务线程全部暂停,只运行 GC 线程;
  2. 弊端:停顿时间过长会导致接口超时、服务卡顿;
  3. 收集器迭代核心目标:降低 STW 时长
  4. 补充:CMS、G1、ZGC 使用并发标记,仅初始标记、重新标记阶段短暂 STW。

六、主流垃圾收集器(分代 / 分区 / 低延迟)

1. Serial 串行收集器(单线程,客户端小程序)

  • Serial Young:新生代复制,Serial Old:老年代标记整理
  • 全程单线程 GC,全部 STW,单核机器简单场景使用,生产极少用

2. Parallel 并行收集器(JDK 默认吞吐量收集器)

  • Parallel Scavenge(新生代)+ Parallel Old(老年代)
  • GC 使用多线程并行回收,用户线程全程暂停;
  • 目标:最大化吞吐量(运行代码时间 / 总运行时间);
  • 适合后台批处理、数据同步,不追求低停顿。

3. CMS 并发标记清除(JDK8 经典老年代收集器,JDK14 废弃)

第一款并发收集器,目标低延迟,老年代专用,搭配 ParNew 新生代。

CMS 四大阶段
  1. 初始标记(STW):仅标记 GC Roots 直接关联对象,停顿极短
  2. 并发标记(无 STW):GC 线程和业务线程同时运行,遍历全部对象
  3. 重新标记(STW):修正并发阶段业务线程新增的垃圾,短暂停顿
  4. 并发清除(无 STW):并发清理垃圾
CMS 致命缺陷
  1. 使用标记清除,产生大量内存碎片,频繁 Full GC;
  2. 并发阶段业务线程持续创建对象,可能出现并发失败,退化为 Serial Old 单线程 Full GC,停顿暴涨;
  3. 浮动垃圾:并发清除阶段新产生对象,本次无法回收,只能等下次 GC。

4. G1(Garbage-First,JDK9 默认,分区式收集器)

打破传统分代,将整个堆划分为多个大小相等的独立 Region(1M~32M); 每个 Region 可动态充当 Eden、Survivor、Old,不再固定新生代 / 老年代边界。

核心优势
  1. 可自定义预期停顿时间(-XX:MaxGCPauseMillis),优先回收垃圾最多的 Region;
  2. 整合复制 + 标记整理,全程无内存碎片;
  3. 兼顾吞吐量与低延迟,替代 CMS,中小型生产通用。
G1 五大阶段

初始标记 (STW) → 并发标记 → 最终标记 (STW) → 筛选回收 (STW)

5. ZGC / Shenandoah(超低延迟收集器,JDK11 + 企业级)

面向超大堆(TB 级),STW 停顿控制在10ms 以内; 使用读屏障、染色指针、负载转发,几乎全程并发,大型高并发服务首选。

七、内存分配与特殊规则

  1. 指针碰撞分配:新生代内存连续,分配对象仅移动指针,速度快;
  2. TLAB 线程本地分配缓冲区:每个线程私有一小块 Eden,避免多线程分配锁竞争;
  3. 大对象直接晋升老年代:超过 - XX:PretenureSizeThreshold 的对象跳过新生代,防止大量复制;
  4. 动态年龄判断:Survivor 中相同年龄对象总大小超过 Survivor 一半,该年龄及以上对象直接晋升老年代,无需等到 15 岁。

八、常见 GC 优化方向

  1. 避免 Full GC:控制大对象、合理设置新生代大小、避免内存泄漏;
  2. 选择合适收集器:批处理用 Parallel,高并发接口用 G1/ZGC;
  3. 调优分代比例:Xms/Xmx 固定堆大小,避免运行时扩容缩容;
  4. 排查内存泄漏:静态集合长期持有对象、ThreadLocal 未清理、IO 流未关闭;
  5. 开启 GC 日志,分析停顿、内存升降、晋升频率。

九、整体总结

  1. GC 本质:自动识别不可达对象,释放堆内存,核心算法是可达性分析
  2. 堆按对象生命周期分代,新生代复制算法、老年代标记整理 / 清除;
  3. GC 存在 STW,收集器演进路线:串行→并行→并发 CMS→分区 G1→超低延迟 ZGC;
  4. Minor GC 高频轻量,Full GC 代价极高,线上业务核心优化目标就是减少 Full GC。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询