这一章主要介绍了Node的内存控制,主要内容有垃圾回收、内存限制、查看内存、内存泄漏、大内存应用等细节。

Nodejs中的GC

Node.js 与 V8 的关系也好比 Java 之于 JVM 的关系,另外 Node.js 之父 Ryan Dahl 在选择 V8 做为 Node.js 的虚拟机时 V8 的性能在当时已经领先了其它所有的 JavaScript 虚拟机,至今仍然是性能最好的,因此我们在做 Node.js 优化时,只要版本升级性能也会伴随着被提升。
V8 对每个进程分配的运行内存,在32位系统中约为700MB,而在64位系统中约为1.4GB

什么是垃圾?

JavaScript中会被判定为垃圾的情形如下:
对象不再被引用
对象不能从根上访问到

V8垃圾回收机制

在V8中,主要将内存分为新生代和老生代两代。新生代中的对象为存活时间较短的对象,老生代中的对象为存活时间较长或常驻内存的对象。V8堆的整体大小就是新生代所用内存空间加上老生代的内存空间。
分代示意图.png
--max-old-space-size命令行参数可以用于设置老生代内存空间的最大值, --max-new-space-size命令行参数则用于设置新生代内存空间的大小的。比较遗憾的是,这两个最大值需要在启动时就指定。这意味着V8使用的内存没有办法根据使用情况自动扩充,当内存分配过程中超过极限值指定。这意味着V8使用的内存没有办法根据使用情况自动扩充,当内存分配过程中超过极限值时,就会引起进程出错。

为什么要分成新老两代?

垃圾回收算法有很多种,但是并没有一种是胜任所有的场景,在实际的应用中,需要根据对象的生存周期长短不一,而使用不同的算法,已达到最好的效果。在V8中,按对象的存活时间将内存的垃圾回收进行不同的分代,然后分别对不同的内存施以更高效的算法。

新生代内存空间的垃圾回收

回收新生代对象主要采用复制算法(Scavenge 算法)加标记整理算法。而Scavenge 算法的具体实现,主要采用了Cheney算法。
新生代的内存空间分成了两个:from、to,内存分配的时候只能分配到from空间中,当垃圾回收触发的时候,将from中存活的对象复制到to空间中,from空间被释放,然后from和to角色置换。当某一个对象第二次经历“复制”时,他就会被认为是存活时间久了,被分配到了老生代内存空间,或者当to的空间已经被使用了25%,后续的对象也将会被分配到老生代,对象从新生代移动到老生代中的过程称为晋升。
设置25%这个限制值的原因是当这次Scavenge回收完成后,这个To空间将变成From空间,接下来的内存分配将在这个空间中进行。如果占比过高,会影响后续的内存分配。对象晋升后,将会在老生代空间中作为存活周期较长的对象来对待,接受新的回收算法处理。
Scavenge的缺点是只能使用堆内存中的一半,这是由划分空间和复制机制所决定的。但Scavenge由于只复制存活的对象,并且对于生命周期短的场景存活对象只占少部分,所以它在时间效率上有优异的表现。
由于Scavenge是典型的牺牲空间换取时间的算法,所以无法大规模地应用到所有的垃圾回收中。但可以发现,Scavenge非常适合应用在新生代中,因为新生代中对象的生命周期较短,恰恰适合这个算法。
V8的堆内存示意图应当如图
scavenge.png

老生代中的垃圾回收——待学习理解 Link

待学习理解,有点复杂了

高效使用内存

前提了解js作用域/作用域链
变量的主动释放 声明过多的全局变量,会导致变量常驻内存,要直到进程结束才能够释放内存。
delete 清除对象属性或者是解除引用(赋值成null or undefined)能够主动释放内存。

闭包

闭包是JavaScript的高级特性,利用它可以产生很多巧妙的效果。它的问题在于,一旦有变量引用这个中间函数,这个中间函数将不会释放,同时也会使原始的作用域不会得到释放,作用域中产生的内存占用也不会得到释放。除非不再有引用,才会逐步释放。

查看内存使用情况

process.memoryUsage() // 返回一个对象,描述 Node.js 进程的内存使用量(以字节为单位)。
返回:
· rss
· heapTotal
· heapUsed
· external
· arrayBuffers

// 大内存占用情况
/* 
heapTotal 和 heapUsed 指的是 V8 的内存使用情况。
external 指的是绑定到 V8 管理的 JavaScript 对象的 C++ 对象的内存使用。
rss,Resident Set Size,是进程在主内存设备(即总分配内存的一个子集)中占用的空间量,包括所有 C++ 和 JavaScript 对象和代码。
arrayBuffers 是指为 ArrayBuffer 和 SharedArrayBuffer 分配的内存,包括所有 Node.js Buffer。 这也包含在 external 值中。 当 Node.js 用作嵌入式库时,此值可能是 0,因为在这种情况下可能不会跟踪 ArrayBuffer 的分配。
*/
const total = [];
setInterval(function ()
{
    total.push(new Array(20 * 1024 * 1024)); // 大内存占用
    print();
}, 1000);
/**
 * 单位为字节格式为 MB 输出
 */
function format(bytes)
{
    return (bytes / 1024 / 1024).toFixed(2) + ' MB';
};

/**
 * 封装 print 方法输出内存占用信息
 */
function print()
{
    const memoryUsage = process.memoryUsage();
    console.log(JSON.stringify({
        rss: format(memoryUsage.rss),
        heapTotal: format(memoryUsage.heapTotal),
        heapUsed: format(memoryUsage.heapUsed),
        external: format(memoryUsage.external),
    }));
};

运行结果如图所示
内存超出1.4G.png

奇怪的是rss明显超出了1.4G,但是并没有因为内存超出而中止,不知道node v14的内存限制是怎么样的,版本提升和书里描述的有些出入

内存泄露

什么是内存泄漏?
内存泄漏是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
说白了就是对象没有被回收一直占用内存

内存泄露优化

内存泄漏优化解除引用确保占用最少的内存可以让页面获得更好的性能。而优化内存占用的最佳方式,就是为执行中的代码只保存必要的数据。一旦数据不再有用,最好通过将其值设置为 null 来释放其引用——这个做法叫做解除引用(dereferencing)
解除一个值的引用并不意味着自动回收该值所占用的内存。解除引用的真正作用是让值脱离执行环境,以便垃圾收集器下次运行时将其回收。
在业务不需要用到的内部函数,可以重构在函数外,实现解除闭包免创建过多生命周期较长的对象,或将对象分解成多个子对象
避免过多使用闭包
注意清除定时器和事件监听器Nodejs
中使用 stream 或 buffer 来操作大文件,不会受 Nodejs 内存限制
使用 redis 等外部工具缓存数据

内存当缓存

// to do

队列状态

// to do

内存检测工具

node-heapdump
heapdump是一个dumpV8堆信息的工具,node-heapdump
node-profiler
node-profiler 是 alinode 团队出品的一个 与node-heapdump 类似的抓取内存堆快照的工具,node-profiler
Easy-Monitor
轻量级的 Node.js 项目内核性能监控 + 分析工具,https://github.com/hyj1991/easy-monitor
Node.js-Troubleshooting-Guide
Node.js 应用线上/线下故障、压测问题和性能调优指南手册,Node.js-Troubleshooting-Guide
alinode
Node.js 性能平台(Node.js Performance Platform)是面向中大型 Node.js 应用提供 性能监控、安全提醒、故障排查、性能优化等服务的整体性解决方案。alinode
// to do

疑问:垃圾回收何时进行?node v14版本的内存限制不是1.4g,是多少?

思维导图 右键新标签页打开
内存控制.png

参考文章:

  1. https://zhuanlan.zhihu.com/p/72380507
  2. https://zhuanlan.zhihu.com/p/40604815
  3. https://segmentfault.com/a/1190000004934938
  4. https://zhuanlan.zhihu.com/p/72380507
  5. https://zhuanlan.zhihu.com/p/352323793
  6. https://zhuanlan.zhihu.com/p/103110917
最后修改:2021 年 06 月 28 日
如果觉得我的文章对你有用,请随意赞赏

发表评论

🎲