5.1 V8的垃圾回收机制与内存限制

38 阅读3分钟

这一小节是全章的基础,朴灵作者先从垃圾回收(GC)的通用原理讲起,然后深入V8的具体实现,最后解释Node为什么有内存上限。理解这里,你就知道为什么Node进程内存一般不会超过1.5GB,以及为什么大对象处理要小心。

5.1.1 垃圾回收算法基础

作者先回顾了经典GC算法(很多读者说这一部分像计算机组成原理复习):

  1. 引用计数(Reference Counting)

    • 每个对象记录被引用次数,0时回收。
    • 优点:简单、实时。
    • 缺点:循环引用无法回收(A引用B,B引用A,计数永不为0)。
    • 现代浏览器基本不用(IE老版本用过)。
  2. 标记-清除(Mark-Sweep)

    • 从根对象(全局、栈)开始标记可达对象。
    • 清除未标记对象。
    • 优点:解决循环引用。
    • 缺点:回收后内存碎片化(不连续)。
  3. 标记-整理(Mark-Compact)

    • 标记后,把存活对象往一端移动,整理碎片。
    • 缺点:STW(Stop The World)时间更长。
  4. 分代回收(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过程会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 高效使用内存,会讲闭包、内存泄漏常见场景(超级实用!)。