WdBly Blog

懂事、有趣、保持理智

周维的个人Blog

懂事、有趣、保持理智

站点概览

周维 | Jim

603927378@qq.com

推荐阅读

Node中的内存控制

Node中的内存控制

基于V8的内存限制

由于V8的限制,Node只能使用V8的部分内存(64位系统下约为1.4 GB,32位系统下约为0.7 GB),这导致了Node无法读取超过2G的文件。

V8的内存分配基于堆内存分配。

Stack memory内存空间由操作系统自动分配和释放,Heap Memory内存空间手动申请和释放的

在V8中,主要将内存分为新生代和老生代两代。新生代中的对象为存活时间较短的对象,老生代中的对象为存活时间较长或常驻内存的对象。

  • V8使用堆内存来分配对象

  • 现代的垃圾回收算法中按对象的存活时间将内存的垃圾回收进行不同的分代,然后分别对不同分代的内存施以更高效的算法。

  • V8将内存空间分为了 新生代内存空间老生代内存空间

  • 新生代内存的最大值在64位系统和32位系统上分别为32 MB和16 MB。

新生代内存空间

  • 新生代中的对象主要通过Scavenge算法进行垃圾回收,而Scavenge算法采用了Cheney算法

  • Cheney算法将新生代空间分为了 From空间To空间,缺点是只能使用堆内存中的一半。

  • 当进行垃圾回收时,会检查From空间中的存活对象并复制到To空间中,然后清空From空间,最后From空间和To空间角色交换,完成一次回收。

image.png

  • 当一个对象经过复制(也就是垃圾回收)后依然存在,则在下次垃圾回收时, 则将其移动到老生代内存空间进行管理。

  • 当从Form向To复制时,如果To的使用超过了25%,则直接将对象移动到老生代内存空间

Scavenge的缺点是只能使用堆内存中的一半,这是由划分空间和复制机制所决定的。但Scavenge由于只复制存活的对象,并且对于生命周期短的场景存活对象只占少部分,所以它在时间效率上有优异的表现。(回收效率高时间快,是典型的牺牲空间换取时间的算法)

Scavenge算法每次是复制活对象,是因为新生代中活着的对象较少。

老生代内存空间

  • 主要采用Mark-Sweep(标记清除)和Mark-Compact相结合的方式进行垃圾回收。

  • Mark-Sweep标记阶段遍历所有对象,标记活着的对象,在清理阶段清理未被标记的对象。

Mark-Sweep最大的问题是在进行一次标记清除回收后,内存空间会出现不连续的状态,内存碎片化问题。导致后续在总空间足够的情况下,无法的大对象分配空间的问题。

  • Mark-Compact(标记整理): 由于上面的碎片化问题,Mark-Compact被提出,在每次标记完活对象后,将内存对象向一边移动,在清理时,直接清理另外一边即可。

image.png

增量标记(incremental marking)

由于V8每次的垃圾回收都会暂停JS线程的执行(为了保证内存对象的同步,防止在清理过程中新的对象别清理掉)。 那么需要优化垃圾回收执行时间。

  • 增量标记:将标记阶段分为多步执行。
  • 延迟清理(lazy sweeping)
  • 增量式整理(incremental compaction),

由V8的内存管理和回收机制可以看出,老生代内存限制大小是合理的,因为每次垃圾回收的时间完全影响了Node主线程代码的执行。

高效使用内存

  • 全局变量常驻老生代内存空间,不会被回收。

  • 闭包中返回的函数,如果使用了外边作用域的变量,那么即使该函数执行完毕,其内部的变量也不会被回收。

function foo() { var a = 1; return function() { console.log(a); } } // 执行后a不会被回收 foo()();
  • process.memoryUsage()查看内存使用情况
{ rss: 21057536, // 常驻内存 heapTotal: 7159808, // V8申请的总内存 heapUsed: 4460440, // 已经使用的内存 external: 8224 }

我们注意到常驻内存的值大于V8申请的总内存值,只是因为Node的内存并不全是V8在分配,这类内存别称之为堆外内存,堆外内存可以突破V8内存限制的问题,如Buffer内存的分配。

  • 谨慎的使用内存当做缓存:当了解的V8的垃圾回收后,我们知道,每次垃圾回收都会扫描内存空间中的对象,缓存对象也不例外,造成很大浪费(学到了),最好还是上redis或者其它缓存库。

使用Node内存作为缓存的另一个问题是进程中的缓存无法共享,导致物理内存的浪费。

  • 通过stream模块操作大文件模块, stream继承自EventEmitter,具备基本的自定义事件功能。

  • 也可以通过Buffer来操作大文件,Buffer不会受到V8的内存限制。

参考资源:
《深入浅出Node.js》

提交

全部评论0

暂时没有评论...