# 内存控制
# V8的垃圾回收
在V8中,主要将内存分为新生代和老生代两部分。新生代中的对象为存活时间较短的对象,老生代中的对象为存活时间较长或常驻内存的对象。
对于新生代的垃圾回收,主要通过Scavenge算法进行垃圾回收。它将内存一分为二,每一部分空间称为semispace。在这两个semispace空间中,只有一个处于使用中,另一个处于闲置状态。处于使用状态的semispace称为From空间,处于闲置状态的空间称为To空间。当我们分配对象时,先是在From空间进行分配。当开始进行垃圾回收时,会检查From空间中的存活对象,这些存活对象将被复制到To空间中,而非存活对象占用的空间将会被释放。完成复制后,From空间和To空间的角色发生对换。
Scavenge的缺点是只能使用堆内存的一半,但是由于只复制存活对象,且对于生命周期短的场景(新生代)存活对象只占少部分。
当一个对象多次复制依然存活,会被认为是生命周期较长的对象。这种较长生命周期的对象会被移动到老生代中。
老生代的垃圾回收采用Mark-Sweep & Mark-Compact。对于老生代内存,对象生命周期较长,所以Mark-Sweep在标记阶段遍历堆中的所有对象,标记活着的对象,在随后的清除阶段,只清除未被标记的对象。Mark-Sweep最大的问题是一次标记清除后,内存空间会出现不连续的状态,Mark-Compact作为改进,会将活着的对象移动整理。
在V8的分代式垃圾回收中,一次小垃圾回收只收集新生代,由于新生代内存配置较小,存活对象通常较少,所以全停顿影响不大。对于老生代 ,一次全量Mark-Sweep-Compact会造成比较长时间的全停顿。所以会有增量标记(incremental marking),延迟清理(lazy sweeping)和增量整理(incremental compaction)。
| 回收算法 | Scavenge | Mark-Sweep | Mark-Compact |
|---|---|---|---|
| 速度 | 最快 | 中等 | 最慢 |
| 空间开销 | 双倍空间 | 少(有碎片) | 少(无碎片) |
| 是否移动对象 | 是 | 否 | 是 |
# 常见内存泄露原因
# 缓存
通常会用一个对象作为缓存,但是这个缓存缺乏一个过期机制,可能会随着运行不断增长。
解决方案:使用LRU、LFU这种带有过期机制的缓存,或者直接使用Redis、Memcached这种缓存解决方案。
# 队列消费不及时
JS自行实现队列,当消费速度小于入队速度,就会内存泄漏。
可以对队列长度进行监控,或者添加超时机制。也可以使用开源的消息队列如Kafka。