内存分配和垃圾回收

内存分配

  1. 数据存储, 基本类型存储在栈中, 引用类型存储在堆中
  2. 在数据定义是分配内存空间

垃圾回收策略

在 JavaScript 内存管理中有一个概念叫做‘可达性’,就是那些以某种方式可访问或者说可用的值,它们被保证存储在内存中,反之不可访问则需回收

  1. 引用计数把“对象是否不再需要”简化定义为“对象有没有其他对象引用到它”。如果没有引用指向该对象(零引用),对象将被垃圾回收机制回收。缺陷是无法对循环引用的变量进行回收
  2. 标记清除把“对象是否不再需要”简化定义为“对象是否可以获得”。

垃圾回收

  1. 引用计数
  2. 标记清除 =》 主要的垃圾回收机制

标记清除

标记清除执行流程

  1. 垃圾收集器将内存中的所有变量标记为0
  2. 从各个根对象开始遍历,修改非垃圾的节点标记
  3. 清理标记为0的变量
  4. 重新将所有变量标记为0 等待下一次遍历

优点
实现简单

问题

  1. 内存碎片
  2. 分配速度慢

可以通过标记整理算法来优化上述问题

引用计数

引用计数执行流程

  1. 当声明了一个变量并且将一个引用类型赋值给该变量的时候这个值的引用次数就为 1
  2. 如果同一个值又被赋给另一个变量,那么引用数加 1
  3. 如果该变量的值被其他的值覆盖了,则引用次数减 1
  4. 当这个值的引用次数变为 0 的时候,说明没有变量在使用,这个值没法被访问了,回收空间,垃圾回收器会在运行的时候清理掉引用次数为 0 的值占用的内存

优点

  1. 立即回收
  2. 不需要暂停js执行

缺点

  1. 计数器也消耗内存
  2. 无法回收循环引用

v8的垃圾回收机制

v8垃圾回收在标记清除的基础上,有一个分代式的垃圾回收策略

分代式垃圾回收策略

标记清除在每次垃圾回收时都要检查内存中所有的对象,这样的话对于一些大、老、存活时间长的对象来说同新、小、存活时间短的对象一个频率的检查很不好,因为前者需要时间长并且不需要频繁进行清理,后者恰好相需要及时清理

新生代垃圾回收

新生代分为使用区和空闲区,但使用区快被写满时就执行一次垃圾回收,对在使用的对象进行标记并复制到空闲区中,随后进入到垃圾清理阶段,将非活动对象占用空间清理掉;但一个对象对象经过多次交换后仍然存在则会被认为是生命周期较长的对象,会被移入到老生代中,采用老生代的垃圾策略进行管理;如果复制一个对象到空闲区时,空闲区空间占用超过了 25%,那么这个对象会被直接晋升到老生代空间中

老生代垃圾回收

清除阶段老生代垃圾回收器会直接将非活动对象,也就是数据清理掉;同时会使用标记整理来优化内存空间

缓冲区

每当新(new-space)空间的对象被旧(old-space)空间的对象引用时, 这个旧空间对象的key将会被记录下来,防止老生代中的引用的数据被误删除;为垃圾回收的优化做支持

优化机制

  1. 增量标记
  2. 延迟清除