这是一篇沉浸式长文,建议在专属页面阅读 → 进入完整版
这是 Field Note 系列的第八篇,姐妹篇是 《一行 JS 的一生 · QuickJS 源码详解》(讲一个引擎全栈)和 《V8 是怎么把 JS 跑快的》(讲多层 JIT)。本篇相反——横向跨所有语言,讲所有现代运行时怎么收尸。
主线:同一行代码,11 种家族,11 种命运
let list = []
for i in 0..1_000_000:
list.push({ n: i })
list = null // ⭐ 关键这一行
这 17 个字符的代码在不同 GC 里发生的事完全不同:
- CPython refcount:
lst = None同行同步释放 1M 对象,~28 ms 内全部 free。 - Swift ARC:同样同行释放,但需要程序员手动
weak打破循环。 - V8 Orinoco:什么都不立即发生——等下次 minor GC(typically ~50ms 后)。
- JVM ZGC:concurrent,并发标记+整理,应用线程几乎不感觉到。
- Go:tri-color concurrent,下一次 GC pacer 触发时清理。
- Erlang BEAM:GC 只在每个进程内跑——这堆数据如果在进程里,进程死了整片 free。
- Rust:编译期就被 drop 了,根本没运行时 GC。
11 个家族 + 20+ 实现,每个都用上面这段代码作主线。
11 个家族
06 Refcount 07 Mark-Sweep 08 Copying · Cheney 09 Generational
10 Incremental 11 Concurrent 12 Region / per-actor 13 Colored ptr
14 No-GC 15 Hybrid 16 Academic
每章下面都有”主线 4 问”卡片:何时 free、STW 多长、CPU 多少、堆形态如何。
这版深挖了什么
- 同一行代码的 11 种命运:CPython 28ms 同步 free vs ZGC 0.5ms 并发整理 vs Erlang 进程死才 free。
- 每个算法家族的真源码:从 CPython
Py_DECREF到 Go runtime/mgc.go 到 ZGCZBarrier到 Erlanggc_minor。 - 5 个永恒权衡:pause / throughput / memory / 启动 / 心智负担。没有 GC 在 5 个轴都拿满。
- 生产事故簿:Discord Go→Rust、Twitter Ruby→Scala、Pinterest Python tuning、LinkedIn JVM。
- 调优决策树:根据 latency / throughput / memory / 启动 选 GC。
- 60 年家谱:1959 McCarthy mark-sweep → 1969 Cheney 复制 → 1984 Lieberman 分代 → 2014 ZGC → 2023 Generational ZGC。
- 未来方向:Carbon / Vale 的”线性 GC”探索,Rust ownership 之后的下一步。
“McCarthy 在 1959 年说 让程序自动管内存。六十多年了,我们还在讨论怎么管。”
Comments
0 comments