美文网首页
深入浅出Node.js_内存控制

深入浅出Node.js_内存控制

作者: 独木舟的木 | 来源:发表于2018-10-26 14:33 被阅读19次

    内存控制

    内存控制是在海量请求长时间运行的前提下进行探讨的。

    在Node中如何高效地使用内存?

    V8 的垃圾回收机制与内存限制

    V8 的内存限制

    • Node通过 JavaScript 使用内存的限制:64位系统下约为 1.4 GB,32位系统下约为 0.7 GB。
    • Node 基于 V8 构建,所以在Node中使用的 JavaScript 对象基本上都是通过V8自己的方式来进行管理和分配的。

    V8 的对象分配

    • 在 V8 中,所有的 JavaScript 对象都是通过==堆==来分配的。
    • V8 限制堆内存,是基于垃圾回收机制与应用性能的考量,(垃圾回收会引起 JS 线程暂停执行)。

    查看Node 的内存使用量:

    查看进程的内存占用:process.memoryUsage()

    $ node
    > process.memoryUsage();
    { rss: 23175168,      # 进程的常驻内存部分
      heapTotal: 9682944, # 已申请到的堆内存
      heapUsed: 5283000,  # 堆内存当前使用量
      external: 11777 }
    >
    
    # 堆中的内存总量<进程的常驻内存用量
    # 堆外内存:不是通过V8分配的内存。
    # Buffer 对象不经过 V8 的内存分配机制。
    
    # Node 的内存构成:通过 V8 分配的部分 + Node 自行分配的部分。
    # 因此,受到 V8 的垃圾回收机制限制的主要是 V8 的堆内存。
    

    查看系统的内存占用:os.totalmem() 和 freemem()

    查看操作系统的内存使用情况:

    $ node
    > os.totalmem() # 返回系统总内存
    17179869184
    > os.freemem() # 返回系统闲置内存
    4516331520  
    

    Node 启动时可以调整内存使用量:

    node --max-old-space-size=1700 test.js // 单位为MB,设置老生代内存空间最大值
    node --max-new-space-size=1024 test.js // 单位为KB,设置新生代内存空间大小
    

    V8 的垃圾回收机制

    • V8 的垃圾回收策略主要基于==分代式垃圾回收机制==。

    • V8 的内存分代:

      • V8 堆内存:新生代内存+老生代内存。
      • 新生代的内存空间中的对象:存活时间短。
      • 老生代的内存空间中的对象:存活时间长、常驻内存对象。
      V8引擎下的堆内存分配 64位系统 32位系统
      默认老生代内存最大值 1400 MB 700 MB
      默认新生代内存最大值 32 MB 16 MB
      V8 堆内存的最大值 1464 MB (1.4 GB) 732 MB(0.7 GB)
    • 新生代中的对象主要通过 Scavenge 算法进行垃圾回收,Scavenge 算法主要采用 Cheney 算法(采用复制的方式实现垃圾回收From-To)实现。

    • 老生代中的对象主要采用了Mark-Sweep(标记清除)和Marl-Compact(标记整理)相结合的方式进行垃圾回收。

      image

    查看垃圾回收日志:--trach_gc

    # 在 gc.log 文件中得到所有的垃圾回收信息。
    node --trach_gc -e "var a = [];for (var i = 0; i < 1000000; i++) a.push(new Array(100));" > gc.log
    

    查看性能分析数据:--prof

    node --prof test.js
    
    • V8 提供了 linux-tick-processor 工具(Node路径:deps/v8/tools)用于统计日志信息。

      # 将该工具的目录添加到环境变量 PATH 中,执行分析日志命令:
      linux-tick-processor v8.log
      

    高效使用内存

    如何让垃圾回收机制更高效地工作?

    作用域

    • JavaScript 中能形成作用域的方式:函数调用、with、全局作用域。

    • 标识符查找,变量只能向外访问,而不能向内访问;

    • 作用域链;

    • 变量的主动释放:delete 操作、将变量重新赋值。

      // 全局变量,进程退出才会释放
      global.foo = 'I am global object';
      console.log(global.foo); // => 'I am global object'
      
      // 1. delete 操作删除全局变量
      delete global.foo; 
      
      // 2. 重新赋值
      global.foo = undefined; // or null
      console.log(global.foo); // => 'undefined'
      

      💡 在 V8 中通过 delete 删除对象的属性有可能干扰 V8 的优化,所以通过赋值方式解除引用更好。

    闭包:实现外部作用域访问内部作用域中变量的方法

    var foo = function () {
        var bar = function () {
            var local = '局部变量';
            return function () { // 函数作为返回值,且该匿名函数可以访问local
                return local; // 内部作用域可以访问外部对象
            };
        };
        var baz = bar(); // bar() 函数返回的是一个匿名函数,且可以访问local
        console.log(baz());
    }
    

    在正常的 JavaScript 执行中,无法立即回收的内存:

    1. 闭包;
    2. 全局变量;

    慎将内存当缓存

    • 在Node中,任何试图拿内存当缓存的行为都应当被限制。
    • Iru cache

    缓存的解决方案

    进程之间无法共享内存,使用大量缓存的解决方案:采用进程外的缓存,进程自身不存储状态。

    解决方案:

    关注队列状态

    消费速度 < 生产速度时,将会形成堆积:

    日志收集时,选择更高效的文件写入日志,而不是数据库写入日志。

    深度的解决方案:1. 监控队列的长度;2.任意异步调用都应该包含超时机制。

    Bagpipe 的超时模式和拒绝模式。

    内存泄漏排查

    Node内存泄漏检测工具:

    • v8-profiler。由Danny Coastes提供,它可以用于对V8堆内存抓取快照和对CPU进行分析,该项目已有3年没维护了
    • node-heapdump。这是Node核心贡献者之一Ben Noordhuis编写的模块,它允许对V8堆内存抓取快照,用于事后分析。
    • node-mtrace。由Jimb Esser提供,它使用了GCC的mtrace工具来分析堆的使用。
    • dtace。在Joyent的SmartOS系统上,有完善的dtrace工具用来分析内存泄漏。
    • node-memwatch。来自Mozilla的Lloyd Hilaiel贡献的模块,采用WTFPL许可发布。

    大内存应用

    Node 提供了 stream 模块用于处理大文件。

    fs 模块的createReadStream()createWriteStream() 方法可以分别用于创建文件的可读流与可写流。

    process 模块中的 stdin 和 stdout 分别是可读流和可写流的示例。

    const fs = require('fs');
    
    // 读取一个文件,然后将数据写入到另一个文件中
    // 流处理的方式不会受到V8内存限制的影响。
    var reader = fs.createReadStream('in.txt');
    var writer = fs.createWriteStream('out.txt');
    reader.on('data', function (chunk) {
        writer.write(chunk);
    });
    reader.on('end', function () {
        writer.end();
    });
    
    // 上述代码的简写方法:
    var reader = fs.createReadStream('in.txt');
    var writer = fs.createWriteStream('out.txt');
    reader.pipe(writer);
    

    💡

    如果不需要进行字符串层面的操作,则不需要借助 V8 来处理,可以尝试进行纯粹的 ==Buffer== 操作,这不会受到 V8 堆内存的限制。

    相关文章

      网友评论

          本文标题:深入浅出Node.js_内存控制

          本文链接:https://www.haomeiwen.com/subject/kvnktqtx.html