Skip to content
大纲

内存管理

内存数据管理

  • 原始数据类型存储在栈内存中,由操作系统管理
  • 引用数据类型存储在堆内存中,由 V8 引擎管理(注:闭包中的基本类型也是存放在堆中的)

声明周期

  1. 分配所需内存
  2. 使用分配内存(读写)
  3. 释放归还内存

内存的分配和释放在底层语言中可以显性控制(如 c 语言 malloc()free()),在大部分高级语言中为隐性处理;
大部分高级语言中,在变量声明时就完成了内存分配,而内存的释放则由垃圾回收器调控
内存的使用在所有语言中都是显性控制的,如对变量的读写

内存构成

  • 新生代内存区:
  • 老生代内存区
  • 大对象区
  • 代码区
  • Map 区

每个区域都由 内存页 组成(内存管理的最小单位),除了大对象区,其他区域内存页大小为 1MB 大对象区、代码区、Map 区属于老生代区

64 位环境下:新生代内存默认最大 32MB,老生代内存默认最大 1.4GB 32 位环境下:新生代内存默认最大 16MB,老生代内存默认最大 700MB

垃圾回收器

  • 副垃圾回收器(Minor GC):用于新生代内存
  • 主垃圾回收器(Mahor GC):用于整个堆,包括新生代和老生代

全停顿

全停顿(Stop-The-World):V8 在 GC[^1] 时会阻塞 JS 执行(避免 JS 修改正在 GC 的对象),耗时 1s 以上

新生代 GC 中,存活对象少,GC 效率高,全停顿时间短
老生代 GC 中,存活对象多,GC 效率低,全停顿时间长
V8 对老生代 GC 采取了增量标记,将一次听到的标记过程进行分步,减少了最大停顿时间

新生代 GC

新生代内存等分为两块区域(Semi-Space),分别为 From-Space 与 To-Space;
From-Space 是真正使用的内容,To-Space 是空闲的,在 GC 时使用
From-Space 又分为 Nursery 和 Intermediate 两块区域,对象第一次分配时在 Nursery,GC 后转移到 Intermediate

副垃圾回收器 使用了清道夫算法(Scavenge),其实现使用了 Cheney 算法

  1. 广度优先遍历 From-Space 中的对象,将存活的对象复制到 To-Space
  2. 遍历完成后,清空 From-Space
  3. From-Space 与 To-Space 角色互换

新生代中部分对象会转移到老生代中

  • 经过一次 GC 后存活的对象,即 Intermediate 区域
  • 对象复制到 To-Space 时,To-Space 空间使用 25% 以上

老生代 GC

老生代内存分为两块区域,分别为指针区与数据区

主垃圾回收器 使用了 标记、清除、整理 的方式进行 GC

标记(Marking)清除(Sweeping)

标记清除是常用的 GC 机制
当变量进入环境时,将该变量标记为 进入环境 ,进入环境 的变量不能释放
当变量离开环境时,将该变量标记为 离开环境

系统使用三色进行标记

  • 值 00,表示 白色,未被引用
  • 值 10,表示 灰色,已被引用,其引用对象未遍历完成
  • 值 11,表示 黑色,已被引用,其引用对象已遍历完成

开始,所有对象标记为白色,然后从根集开始,已深度优先遍历的方式将访问到的对象进行标记

遍历结束之后,清除标记仍为白色的对象(将内存地址标记为空闲),会造成内存空不连续的情况

内存整理(Compacting)

修改仍存活的对象的内存地址,将不同内存页上的对象进行整合,使内存空间紧凑
该操作常发生在剩余空间不足以存放新晋对象或内存页高度分散等状况
对于引用次数过多的对象对跳过整理以免影响性能

参考资料

内存管理