目录
正在加载目录…
专栏文章
专栏文章
GC 垃圾回收系列
1. GC 总览与历史:自动内存管理的起源与演化 2. GC 基础算法:标记-清除、标记-整理、复制收集、引用计数 3. 分代假说与分代收集:朝生夕死的工程哲学 4. CMS 收集器:Java 第一个并发收集器的设计与缺陷 5. G1 收集器:Region 化堆与可预测停顿 6. ZGC 与 Shenandoah:亚毫秒级停顿的并发移动收集器 7. Go 的 GC:并发三色标记与混合写屏障 8. Python 的内存管理:引用计数与分代循环 GC 9. GC 调优实践:JVM 日志解读与 G1/ZGC 参数策略

GC 总览与历史:自动内存管理的起源与演化

发布于 2026-05-25 14:15 · 最后编辑于 2026-07-31 15:53 · 字数 1,893 👁 112 次阅读

垃圾回收(GC)是自动内存管理的核心机制,让程序员从手动 malloc/free 的陷阱中解放出来。本文梳理 GC 的起源动机、核心权衡,以及各主流语言的选择背景。

目录

章节说明
为什么需要 GC手动管理的问题
GC 的核心权衡吞吐量、延迟、内存占用
历史沿革从 Lisp 到现代 GC
各语言的选择为什么不是所有语言都用 GC

为什么需要 GC

手动内存管理的两类致命错误

悬空指针(Use-After-Free): 释放内存后继续使用

int *p = malloc(sizeof(int));
*p = 42;
free(p);        // 释放内存
printf("%d\n", *p);   // ❌ 未定义行为:内存已被释放
                      // 可能读到垃圾值,可能崩溃,可能被利用为安全漏洞

内存泄漏(Memory Leak): 应该释放的内存没有释放

void process() {
    char *buf = malloc(1024);
    if (error_condition) {
        return;   // ❌ 忘记 free(buf),每次调用泄漏 1KB
    }
    // ...
    free(buf);
}
// 长期运行的服务器程序最终 OOM

这两类错误在 C/C++ 中是最常见、最难调试的 Bug。GC 的根本价值是消除这两类错误

GC 的代价

GC 不是免费的——它用CPU 时间和额外内存换取程序员的认知负担

手动管理GC
内存控制精度精确不精确(GC 决定何时回收)
CPU 开销低(只有 malloc/free)有额外扫描开销
内存峰值低(释放立即生效)高(GC 不立即回收)
停顿可能有 STW(Stop-The-World)
安全性低(悬空指针/泄漏)高(自动管理)

GC 的核心权衡

所有 GC 算法都在三个维度上取舍(来源:《垃圾回收算法手册》,工程实践经验总结):

  • 吞吐量(Throughput):应用线程占 CPU 时间的比例,GC 线程占用越少越好
  • 停顿延迟(Pause Latency):单次 GC 导致应用暂停的最长时间
  • 空间开销(Space Overhead):GC 机制本身需要的额外内存,如引用计数字段、卡表、记忆集、转发表等辅助数据结构

三者无法同时最优,任意 GC 都在其中两项优秀、一项妥协:

gc triangle

GC 类型优先牺牲代表
吞吐量优先吞吐量停顿延迟(长 STW)Serial GC、Parallel GC
延迟优先停顿延迟吞吐量(并发 GC 线程开销)+ 空间开销(转发表等)ZGC、Shenandoah
空间开销优先空间开销吞吐量(原子操作/并发开销)引用计数(CPython、Swift ARC)
均衡三者折中实现复杂度G1

历史沿革

年份事件
1959John McCarthy 在 Lisp 中发明 GC(标记-清除算法)
1960George Collins 提出引用计数
1963Marvin Minsky 提出复制收集算法(Cheney 1970 改进)
1970sSmalltalk 推广分代 GC 思想
1984David Ungar 发表分代假说论文
1990sJava 兴起,GC 进入主流工业应用
1994CMS(Concurrent Mark-Sweep)思想提出
2000Java HotSpot 引入分代收集
2004Azul Systems 发布 Pauseless GC(ZGC 的精神前身)
2006G1 GC 在研究论文中发表
2012G1 在 Java 7u4 正式发布
2018ZGC 在 JDK 11 作为实验特性发布
2020ZGC 在 JDK 15 正式发布(亚毫秒停顿)
2021Go 1.17 并发 GC 成熟,停顿 < 1ms

关键转折点

1959 Lisp GC: McCarthy 设计 Lisp 时意识到手动管理与函数式语言的不兼容性——函数式编程大量产生临时对象,手动管理不可行。GC 是函数式范式的必然产物。

1984 分代假说: David Ungar 观察到"大多数对象年轻时死亡",这一洞察使分代 GC 成为主流,性能提升数倍。

2004 Pauseless GC: Azul 系统在特定硬件上实现了完全无停顿 GC,证明了理论可行性,为 ZGC 等指明了方向。

各语言的选择

语言GC 策略核心原因
Java/JVM分代 GC(G1/ZGC)企业级应用,内存安全优先于极致性能
Go并发三色标记系统编程,低延迟是核心需求,不分代
Python引用计数 + 分代简单实现,C 扩展可控制引用
JavaScript (V8)分代(Orinoco)浏览器环境,用户感知延迟敏感
C#/.NET分代(Gen0/1/2)与 Java 类似定位
Rust无 GC(所有权系统)系统编程,编译时保证内存安全
C/C++无 GC(手动)极致控制,底层系统
SwiftARC(自动引用计数)Apple 生态,确定性析构
Haskell分代(GHC GC)惰性求值产生大量临时对象,GC 必要

为什么 Rust 不用 GC

Rust 用所有权(Ownership)+ 借用检查器(Borrow Checker) 在编译期解决内存安全问题:

fn main() {
    let s1 = String::from("hello");
    let s2 = s1;   // s1 的所有权移交给 s2
    // println!("{}", s1);  // ❌ 编译错误:s1 已被移走
    println!("{}", s2);     // ✅
}   // s2 离开作用域,自动调用 drop,释放内存
// 编译器保证:无悬空指针,无内存泄漏,无 GC 开销

代价是学习曲线陡峭,所有权规则对开发者的认知要求高。

参考资料

  • John McCarthy 1960 年论文:"Recursive Functions of Symbolic Expressions"(GC 的诞生)
  • David Ungar & Randall Smith 1987 年论文:"Generation Scavenging"(分代 GC)
  • 《深入理解 Java 虚拟机》第 3 章 — 周志明
  • GC 基础算法
← 返回列表

评论 (0)

暂无评论,来留下第一条吧。
登录注册 后才能发表评论