# 内存控制

# 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。