{"name":"垃圾回收","id":"编程语言-JAVA-JVM-自动内存管理-垃圾回收","content":"# JVM 垃圾回收（GC）\n\n## 第一性原理层\n\n### 为什么需要垃圾回收\n\n**根本问题**：\n\n> 程序如何判断一块内存是否还“有价值”？\n\n在现代高级语言中：\n\n* 对象的**使用权**由程序逻辑决定\n* 对象的**生命周期**却难以由程序员精确管理\n\n如果完全依赖人工释放：\n\n* 易产生悬挂指针、重复释放\n* 难以应对异常、并发、复杂控制流\n\n**结论**：\n\n> 垃圾回收的本质，是把“内存生命周期管理”从业务逻辑中剥离，交由运行时系统统一治理。\n\n### GC 的本质定义\n\n无论实现如何变化，所有 GC 都在解决同一个问题：\n\n> **在不破坏程序语义的前提下，找出仍然可能被使用的对象，其余内存可以被安全重用。**\n\n抽象为两个不可分割的子问题：\n\n1. **存活对象发现（Live Object Discovery）**\n2. **内存空间重用（Space Reclamation & Reuse）**\n\n> 本文主体聚焦“发现”侧；“重用/分配”侧与回收**对偶**——**回收算法反向约束分配方式**：指针碰撞分配要求连续内存，正是复制 / 整理式压缩回收存在的理由；空闲列表分配容忍碎片，匹配标记-清除。见 [内存结构.md](内存结构.md)。\n\n## 理论模型层\n\n### 存活判定：可达 vs 计数\n\n存活判定有两条理论路线，JVM 选后者：\n\n| 模型 | 判据 | 结局 | 根本原因 |\n| --- | --- | --- | --- |\n| 引用计数 | 被引用次数 | 被否定 | 无法处理**循环引用**，真实语义下需大量额外修正——是**正确性**问题而非性能问题 |\n| 可达性分析 | 是否**仍可被程序访问** | GC 理论基石 | 从 GC Roots 遍历对象图，能到达即存活 |\n\n存活取决于\"是否仍可被访问\"——这是 JVM GC 的第一性理论基础。\n\n### 指针识别：精确 vs 保守\n\n可达性遍历隐含一个被默认掉的前提——遍历前必须知道**栈帧 / 寄存器里哪些字是引用**。这个前提本身是一条独立、跨语言稳定的设计权衡轴：\n\n| 模型 | 判断引用的方式 | 主要代价 | 能否移动对象 | 代表 |\n| --- | --- | --- | --- | --- |\n| 精确式（Exact） | 靠类型元数据准确定位引用 | 编译器须生成引用位置表 | 能 | HotSpot |\n| 保守式（Conservative） | 值\"像地址\"就当引用 | 整数可能被误判为指针 → 误留垃圾 | 不能（不敢改疑似指针） | Boehm GC（C/C++ 无类型安全） |\n\n> 关键推论：**指针识别精度反向决定回收算法的可行域**——保守式不敢移动\"疑似指针\"，封杀复制 / 整理式压缩；唯精确式解锁移动式回收。HotSpot 用 OopMap 换精确性（见\"HotSpot 架构支撑机制\"）正是为此。\n\n辨析（本文两个\"保守\"正交无关，勿混）：\n\n**元信息可得性决定优化空间**——类型信息让 GC 敢移动对象，同构于类型信息让 JIT 敢去虚化 / 内联；缺元信息则只能保守。\n\n### GC Roots：可达性的锚点\n\n原则：**只要程序还能\"直接使用\"的对象，就必须作为根**。据此包括：线程栈局部变量、类静态变量、常量池引用、JNI / 本地代码引用。\n\n### 引用强度与终结：可达性的完整谱系\n\n引用非二元（有 / 无），而是可达性的一条**分级谱系**，让程序**参与\"内存价值排序\"**：\n\n| 引用类型 | 本质定位 | 设计目的 |\n| ---- | ----- | -------- |\n| 强引用 | 必须存活 | 程序正确性 |\n| 软引用 | 可牺牲 | 内存弹性（缓存） |\n| 弱引用 | 不阻止回收 | 生命周期绑定 |\n| 虚引用 | 仅做通知 | 资源回收协调 |\n\n> 理论内核：对象的**可达性级别由到它的最强一条引用链决定**（强可达 > 软可达 > 弱可达 > 虚可达 > 不可达），此偏序**决定回收器清理引用的先后**。\"引用种类\"是工具，\"可达性级别\"才是判定实际作用的状态。\n\n谱系的最弱端（虚可达）连着对象生命的终点，这里有一条稳定约束：\n\n> **死亡必须单调**——对象一旦被判不可达，这个判断不应被回收流程自身逆转。\n\n违反它（对象复活 resurrection）：若回收前的清理钩子能重建引用，判定即失去单调性——单次标记不能定生死，须多趟重扫，回收时机不可测。故稳定设计原则是**终结动作与对象本体解耦**：\n\n| 终结钩子能否触及对象本体 | 能否复活 | 判定单调 | 定位 |\n| --- | --- | --- | --- |\n| 能（可写回引用） | 是 | 否 | 反模式 |\n| 否（只收死亡通知） | 否 | 是 | 稳定解 |\n\n> 把\"死亡干预\"降级为\"死亡通知\"，用不可复活换判定确定性。具体终结器 API 的兴废（`finalize` → `Cleaner`）是这条原则的工程落地。\n\n可达性按强弱排成一条有序链，和事务隔离级别、锁强度是同一种\"状态分级\"建模；而**死亡单调性** ≈ 分布式墓碑（tombstone）、CRDT 单调收敛、事务不可逆提交——都以\"不可逆\"换\"确定性\"。\n\n## 分代：对象行为假说的机制兑现\n\n### 分代收集的理论前提\n\nJVM 并非随意分代，而是基于三条经验假说，每条都兑现为一项具体机制：\n\n1. **弱分代假说**：绝大多数对象朝生夕灭 —— 为何要划**新生代**并对其复制式快速回收\n2. **强分代假说**：活得越久的对象越难死亡 —— 为何要有**对象晋升（tenuring）**与老年代\n3. **跨代引用假说**：跨代引用远少于同代引用 —— 为何**记忆集 / 卡表**足够便宜（见\"HotSpot 架构支撑机制\"）\n\n> GC 分代不是优化技巧，而是**时间局部性在对象寿命上的投影**——分代假说并非公理，可归约到更底层的局部性原理。\n\n### 分代的抽象角色划分\n\n按存活概率把堆切成两类区，每类匹配其假说所指的算法与频率：\n\n| 代 | 本质角色 | 回收算法 | 回收频率 | 依据假说 |\n| --- | --- | --- | --- | --- |\n| 新生代 | 高死亡率区 | 标记-复制 | 高频（Minor GC） | 弱分代 |\n| 老年代 | 高稳定区 | 标记-整理 / 清除 | 低频（Major / Full GC） | 强分代 |\n\n> 分代只是\"堆按属性分区\"的**年龄维度**一例；大小、等大 Region、NUMA 等其它分区维度属堆结构范畴，权威见 [内存结构.md](内存结构.md)。本篇只取年龄维度——它是唯一有分代假说支撑、且直接决定回收算法的键。\n\n> 分代的实质：**同一套发现机制（可达性），按分区的死亡率差异，匹配不同的重用算法与触发频率**。\n\n## 核心回收算法\n\n这是第一性层两大子问题的**空间重用**侧：三种基础算法即三种重用方式，唯一的区别是**对存活对象是否移动、如何移动**。\n\n| 对存活对象 | 算法 | 换来 | 代价（牺牲的三角顶点） | 前置条件 |\n| --- | --- | --- | --- | --- |\n| 不动 | 标记-清除 | 实现简单 | 碎片 → 伤**吞吐** | 无（保守式亦可） |\n| 搬到新空间 | 标记-复制 | 无碎片、分配快 | 牺牲空间 → 伤**内存** | 需精确式 |\n| 原地压缩 | 标记-整理 | 空间连续 | 移动成本 → 伤**延迟** | 需精确式 |\n\n> 一根轴（移动与否）生成三种纯策略，各牺牲不可能三角的一个顶点——选算法即选牺牲哪个顶点。移动类算法（复制 / 整理）是精确式的专利，保守式只能用清除。\n\n## 并发 GC 的两个正交难题\n\n> 标记阶段“看图 vs 改图”，转移阶段“移对象 vs 读旧址”。二者用不同屏障解决——这是 CMS → G1 → ZGC 演进的主线。\n\n### 第一难题：并发标记 —— 看图 vs 改图\n\n根本冲突：**GC 在遍历对象图，程序在修改对象图。**\n\n| 颜色 | 含义        |\n| -- | --------- |\n| 白  | 未访问（可能垃圾） |\n| 灰  | 已访问，未扫描完  |\n| 黑  | 已完全扫描     |\n\n> 核心不变量：**黑对象不能直接引用白对象。**\n\n并发标记的风险是**漏标**：一个已扫完的黑对象，在 GC 不再回看它之后，才新指向某个白对象——GC 遂把这个仍存活的白对象误当垃圾回收。\n\n漏标需**同时**满足两条件：①黑对象新增了指向白对象的引用 ②删除了其他所有到该白对象的路径。破坏任一即可修正，两条路线**均由写屏障实现**：\n\n| 修正思想                | 屏障语义      | 代表  | 破坏的条件 |\n| ------------------- | --------- | --- | ----- |\n| 增量更新（Dijkstra 插入屏障） | 记录新增的引用   | CMS | 条件 ①  |\n| 原始快照 SATB（Yuasa 删除屏障） | 记录被删除的旧引用 | G1  | 条件 ②  |\n\n> 写屏障守护并发**标记**：不漏活对象。\n\n### 第二难题：并发转移 —— 移对象 vs 读旧址\n\n标记只**读**对象图，转移却**改变对象的地址**：GC 把对象搬走后，程序手里的引用仍指向旧址——若不处理即悬垂引用 / 失效副本。\n\n> 读屏障守护并发**转移**：不读到已搬走的对象。\n\n#### 解法：间接层 + 拦截点 + 惰性修正\n\n并发转移的解法是一个**跨域复用的通用模式**——如何在不停止访问者的前提下迁移共享状态：\n\n| 要素      | 作用             | 在 GC 中的体现     |\n| ------- | -------------- | ------------ |\n| **间接层** | 旧身份不立即失效，留一层重定向 | 前向指针（旧址 → 新副本） |\n| **拦截点** | 访问时才发现不一致      | 读屏障（加载引用时检查）  |\n| **惰性修正** | 用到才改，成本摊销      | 自愈（顺手把引用改写为新址） |\n\n横向迁移到其他领域：\n\n| 场景        | 间接层         | 拦截点   | 惰性修正   |\n| --------- | ----------- | ----- | ------ |\n| GC 并发转移   | 前向指针        | 读屏障   | 自愈改址   |\n| 数据库 MVCC  | 旧版本链        | 可见性判断 | vacuum 回收 |\n| 在线分片迁移    | 路由表 / 一致性哈希 | 请求代理  | 逐步搬迁   |\n| 虚存 / COW  | 页表间接        | 缺页中断  | 写时才拷贝  |\n\n## HotSpot 架构支撑机制\n\n前述原理（可达性、分代、并发屏障）要在 HotSpot 落地，都撞上\"精确且全量地做太贵\"。四个机制沿**两条代价摊销链**展开：\n\n### 链一 · 让精确式遍历可负担\n\n| 机制 | 解决什么 | 手段 / 代价 |\n| --- | --- | --- |\n| OopMap | 遍历时如何**准确**知道栈 / 寄存器哪里是引用（精确式的落地，见\"指针识别\"） | 预记录引用偏移，空间换时间 |\n| Safepoint / Safe Region | 处处生成 OopMap 太贵 | 只在少数点（方法调用、循环回边等）生成，世界只在这些点暂停——GC 无法任意时刻启动的根因 |\n\n### 链二 · 让分代回收不扫全堆\n\n| 机制 | 解决什么 | 手段 / 代价 |\n| --- | --- | --- |\n| 记忆集（Remembered Set） | 兑现跨代引用假说：Minor GC 不必扫全老年代（见\"分代\"） | 抽象结构，记录\"非收集区 → 收集区\"的引用来源 |\n| 卡表（Card Table） | 记忆集的具体实现 | 按卡页（HotSpot 512B）映射为字节数组，脏页置位；**精度换性能** |\n\n> 卡表由**写屏障**维护（引用赋值时置脏）——即并发标记那个写屏障的另一用途；此\"写屏障\"与 JMM 内存屏障同名异指。同属\"增量维护视图\"模式）。\n\n## 垃圾收集器 = 策略组合体\n\n没有\"最好的收集器\"，只有\"在三角上选择牺牲谁\"——每个收集器都是三大指标间的一组固定取舍。\n\n### 不可能三角：为何不能三者兼得\n\n| 指标 | 含义 | 与另两者的张力 |\n| --- | --- | --- |\n| 吞吐量 | GC 占用的 CPU 越少，程序可用 CPU 越多 | 要低延迟需并发，并发要抢 CPU → 伤吞吐 |\n| 延迟 | 单次停顿时长 | 要高吞吐可攒一批集中回收（长停顿）；要省内存则堆小、GC 频繁 |\n| 内存占用 | 达成目标所需的堆 / 元数据开销 | 并发、分区、染色都需额外空间 → 换延迟 |\n\n> 三者不可同时最优（GC 领域的 RUM 三角，见\"横向原理关联\"）：优化任一顶点必以另一或两个为代价。收集器选择 = 定位在三角的哪个角。\n\n### 典型收集器的取舍定位\n\n| 收集器 | 关键机制 | 机制如何导致该结果 | 换来 | 牺牲的顶点 |\n| --- | --- | --- | --- | --- |\n| Serial | 单线程 | 无线程协调、无并发数据结构 → 代码最简、额外内存最少 | 极简、内存省 | 延迟（长 STW） |\n| Parallel | 多线程并行回收 | 多核并行缩短单次回收总耗时 → GC 占 CPU 比例最低 | 最高吞吐 | 延迟（不可控停顿） |\n| CMS | 并发标记-清除 | 标记与程序并发 → STW 只剩短暂标记；但不整理留碎片、并发期产生浮动垃圾 | 低延迟 | 吞吐 + 内存（碎片 / 浮动垃圾） |\n| G1 | 分区 + 增量回收 | 停顿 ∝ 本次所选 region 数（可控），不再 ∝ 堆大小 → 可反推目标 | **可预测停顿**（可设目标） | 吞吐（屏障开销） |\n| ZGC | 染色指针 + 读屏障 | 引用修正挪到并发期 + 读时惰性自愈 → STW 只剩根扫描，与堆无关 | 亚毫秒停顿，且**不随堆增长** | 吞吐（5-15%）+ 内存（额外开销） |\n\n### 选型：由主关注点倒推\n\n| 主关注点 | 场景 | 选择 |\n| --- | --- | --- |\n| 吞吐量 | 批处理、数据分析、科学计算 | Parallel |\n| 延迟 | SLA 服务、交互式、大堆低延迟 | G1 → ZGC / Shenandoah |\n| 内存 / 简单 | 客户端、嵌入式、小堆 | Serial |\n\n> 演进主线是一条**在三角上腾挪**的轨迹：Serial 单线程 → Parallel 并行化榨吞吐 → CMS / G1 并发化换延迟 → ZGC 用染色指针让停顿**与堆大小解耦**。每一步都在\"用新机制把某个顶点的代价压得更低\"。\n\n## 现代 GC 的演进趋势\n\n### 驱动力：为何一直朝低延迟走\n\n> **对象规模 ↑ → 堆容量 ↑ → 人类对停顿的容忍 ↓**\n\n堆越大，\"一次停顿处理完全堆\"的代价越不可接受。这把**不可能三角**的权重持续压向**延迟**顶点——演进史就是一部\"不断把延迟的代价往下压、且让它不再随堆规模增长\"的历史。\n\n### 变的方向：代价往哪腾挪\n\n| 维度 | 手段 | 把代价挪去了哪 |\n| --- | --- | --- |\n| 停顿 | STW 阶段并发化 | 停顿 → 换 CPU / 屏障开销（伤吞吐） |\n| 全堆 → 局部 | Region 化承载分代 | 全堆扫描 → 换记忆集维护开销（伤吞吐 / 内存） |\n| 遍历 → 转移 | 读屏障使转移可并发 | 集中修正 → 摊到每次读取（伤吞吐） |\n\n> 三者不是\"新方案取代旧方案\"：Region 是**承载**分代的方式（分代仍在，ZGC 已有分代版）；读屏障守护并发**转移**、写屏障守护并发**标记**，是**正交两难题**的两个解，非谁替代谁。每一步的共性是**用空间 / 吞吐换延迟**——三角不曾被打破，只是选择了牺牲谁。\n\n### 不变量：什么几十年没变\n\n演进只动机制，不动地基。始终不变的是：\n\n- **判据**：可达性分析（存活 = 可达）\n- **结构**：发现 / 重用对偶\n- **约束**：不可能三角（吞吐 / 延迟 / 内存三元互斥）\n\n## 横向原理关联\n\n> GC 把「局部性、快照隔离、RUM 三角、屏障」这几条跨域稳定原理，在“内存生命周期”这一维度上重演了一遍。\n\n| 本文原理        | 上位/同域枢纽       | 同构关系       | 迁移价值           |\n| ----------- | ------------- | ---------- | -------------- |\n| 分代假说        | 局部性原理         | 对象死亡率的时间局部性 | 缓存分层 / JIT 热点分层同模式 |\n| 不可能三角       | RUM 猜想        | 三元互斥的结构同构  | 索引 / 存储引擎选型同一坐标系 |\n| 三色标记 + SATB | 快照隔离 / MVCC   | 对可达性计算做快照隔离 | 无锁读写分离的通用范式    |\n| STW 安全点     | 并发屏障 / 全局同步点  | 全局一致点暂停    | 分布式 stop-the-world 同理 |\n| 记忆集 / 卡表    | 增量视图维护 / 脏页追踪 | 空间换时间的增量重扫 | 物化视图 / 缓存失效同模式 |\n| 死亡单调性       | 分布式墓碑 / CRDT 单调 | 以不可逆换判定确定性 | 逻辑删除 / 事务提交同模式 |\n\n### 归约一：分代假说 = 局部性原理的时间变体\n\n“朝生夕灭”不是 GC 的独有经验，而是**时间局部性**在对象生命周期上的投影：最近分配的对象最可能很快死亡，活过阈值的对象倾向继续存活——这与“最近访问的数据最可能再次访问”是同一条统计规律的两个方向。分代按年龄分层治理，与存储层次按访问频率分层、缓存 LRU、JIT 分层编译共享同一“按局部性分层”母模式。\n\n> 因此分代的稳定性来自局部性原理；一旦程序违反弱分代假说（对象长寿），分代反成负担（多一次幸存者拷贝），正如缓存对无局部性的访问流失效。\n\n### 同构二：三色标记 + SATB = 可达性计算的快照隔离\n\n并发标记的根本冲突“GC 在看对象图 / 程序在改对象图”，与数据库“长事务在读 / 其他事务在写”是**同一矛盾**。SATB 的解法与 MVCC 快照隔离逐点对应：\n\n| SATB（可达性快照）    | 快照隔离 / MVCC（数据快照）    |\n| -------------- | -------------------- |\n| 初始标记时刻冻结存活集    | 事务开始时刻冻结可见版本         |\n| 写前屏障记录被覆盖的旧引用  | 更新保留旧行版本而非原地覆盖       |\n| 快照后新分配对象隐式存活   | 快照后新插入行对读事务不可见       |\n| 浮动垃圾，下轮 GC 回收   | 死元组膨胀，靠 vacuum 回收    |\n\n> SATB 本质是“为可达性计算加了一层快照隔离”：GC 扮演一个长读事务，写前屏障扮演 MVCC 的旧版本保留。二者都用“暂留已死状态”换“无阻塞并发”。\n\n### 辨析三：GC 写屏障 ≠ JMM 内存屏障\n\n两个“屏障”名字相近、层次无关，必须划清：\n\n| 维度   | GC 写屏障         | JMM 内存屏障（memory fence） |\n| ---- | -------------- | ---------------------- |\n| 解决问题 | 引用图变化的漏标 / 跨代引用 | 指令重排序、跨线程可见性           |\n| 机制   | 注入引用写操作的钩子，记录引用 | 约束 CPU / 编译器的读写顺序      |\n| 关乎   | GC 正确性（不漏标）    | 并发语义（可见性 / 有序性）        |\n| 权威源  | 本文             | [JAVA内存模型.md](/编程语言/JAVA/JVM/JAVA内存模型.md)            |\n\n> GC 写屏障“记录写了什么引用”，JMM 内存屏障“约束写的顺序何时可见”。\n\n## 关联内容（自动生成）\n\n- [/编程语言/JAVA/JVM/自动内存管理/内存结构.md](/编程语言/JAVA/JVM/自动内存管理/内存结构.md) 本文聚焦“存活发现”侧，其对偶的“空间重用/分配”侧与堆的多维分区（大小/Region/NUMA）权威在此，二者互为表里\n- [/编程语言/JAVA/JVM/自动内存管理/调优.md](/编程语言/JAVA/JVM/自动内存管理/调优.md) 本文定义的不可能三角与分代假说是其权威源，调优篇在此基础上做选型决策与失配信号的特化应用\n- [/编程语言/JAVA/JVM/JAVA内存模型.md](/编程语言/JAVA/JVM/JAVA内存模型.md) “辨析三”指出 GC 写屏障与 JMM 内存屏障同名异指，此文是后者（可见性/重排序约束）的权威源\n- [/编程语言/JAVA/JVM/JVM.md](/编程语言/JAVA/JVM/JVM.md) 垃圾回收是 JVM 自动内存管理的核心子系统，此文提供其所处的整体架构与运行机制上下文\n- [/编程语言/JAVA/JVM/类加载机制.md](/编程语言/JAVA/JVM/类加载机制.md) 类元数据与类加载器构成一类特殊的 GC Roots，类的卸载是可达性判定在方法区维度的延伸\n- [/计算机系统/程序结构和执行/存储器层次结构.md](/计算机系统/程序结构和执行/存储器层次结构.md) 分代假说是时间局部性在对象寿命上的投影，与存储层次按访问频率分层共享同一“按局部性分层”母模式\n- [/中间件/数据库/数据库系统/事务管理/事务.md](/中间件/数据库/数据库系统/事务管理/事务.md) 三色标记 + SATB 与 MVCC 快照隔离逐点同构，都以“暂留已死状态”换无阻塞并发\n- [/中间件/数据库/索引.md](/中间件/数据库/索引.md) GC 吞吐/延迟/内存不可能三角与索引领域的 RUM 猜想是同一三元互斥结构，构成选型的同一坐标系\n- [/软件工程/架构/系统设计/缓存.md](/软件工程/架构/系统设计/缓存.md) 记忆集的增量视图维护、软引用的可牺牲缓存语义，与缓存分层及失效策略同模式\n","metadata":"tags: ['编程语言', '操作系统', '并发编程']","hasMoreCommit":false,"totalCommits":2,"commitList":[{"date":"2026-07-10T17:07:58+08:00","author":"MY","message":"docs(JVM): 更新垃圾回收文档结构和内容优化","hash":"a298194d4ebd92f8537372ff1a5bd5fd1641b8d8"},{"date":"2026-06-29T18:04:46+08:00","author":"MY","message":"docs(agent-constraints): 移除过时的工具使用规范","hash":"fb50d1ddc7bf6d5247c100358995603321292093"}],"createTime":"2026-06-29T18:04:46+08:00"}