这一小节是全章的基础,朴灵作者先从垃圾回收(GC)的通用原理讲起,然后深入V8的具体实现,最后解释Node为什么有内存上限。理解这里,你就知道为什么Node进程内存一般不会超过1.5GB,以及为什么大对象处理要小心。
5.1.1 垃圾回收算法基础
作者先回顾了经典GC算法(很多读者说这一部分像计算机组成原理复习):
-
引用计数(Reference Counting)
- 每个对象记录被引用次数,0时回收。
- 优点:简单、实时。
- 缺点:循环引用无法回收(A引用B,B引用A,计数永不为0)。
- 现代浏览器基本不用(IE老版本用过)。
-
标记-清除(Mark-Sweep)
- 从根对象(全局、栈)开始标记可达对象。
- 清除未标记对象。
- 优点:解决循环引用。
- 缺点:回收后内存碎片化(不连续)。
-
标记-整理(Mark-Compact)
- 标记后,把存活对象往一端移动,整理碎片。
- 缺点:STW(Stop The World)时间更长。
-
分代回收(Generational Collection)
- 核心思想:大多数对象“朝生夕死”(新生代存活率低),少数对象长寿。
- 把堆分为新生代(Young Generation)和老生代(Old Generation)。
- 新生代频繁小GC,老生代少量大GC。
V8就是基于分代 + 标记-清除/整理的现代GC。
5.1.2 V8的垃圾回收机制
V8堆结构(书中有经典分代图):
-
新生代(New Space):小(默认16~32MB),分为From/To两个半区(Semi-Space)。
- 新对象先分配在这里。
- GC算法:Scavenge(复制算法)。
- 标记存活对象 → 复制到To区 → 翻转From/To角色。
- 速度极快(只需复制存活对象,通常很少)。
- 存活几次(默认2次)后晋升到老生代。
-
老生代(Old Space):大(默认700MB~1.4GB)。
- GC算法:Mark-Sweep + Mark-Compact。
- 主GC:标记-清除(快速但碎片)。
- 碎片多时:标记-整理(慢但连续)。
- 还有增量标记(Incremental Marking)减少卡顿(现代V8更强,并发标记)。
- GC算法:Mark-Sweep + Mark-Compact。
GC过程会STW(暂停JS执行),但V8优化得很好:
- 新生代GC几毫秒。
- 老生代GC几十到几百毫秒(大堆时更长)。
5.1.3 Node的内存限制
关键问题:为什么Node有内存上限?
- V8设计时针对浏览器(单个标签页不该吃太多内存)。
- 堆内存上限(64位约1.4GB,32位约0.7GB)。
- Node启动参数可调:
node --max-old-space-size=4096 # 单位MB,最大约4GB(现代V8支持更高) node --max-semi-space-size=128 # 新生代大小
作者强调:
- Node不适合内存密集任务(如大图片处理、视频转码),容易OOM。
- 适合I/O密集(内存占用稳定)。
下面是一些经典的V8堆结构图、新生代Scavenge过程图、老生代Mark-Sweep图(几乎所有读者笔记都会重绘这些):
这一小节读完,你就明白为什么Node进程内存不会无限增长,以及为什么大数组/对象要小心分配。
下一个小节5.2 高效使用内存,会讲闭包、内存泄漏常见场景(超级实用!)。