【8.16】nodejs 原理学习 - 内存控制(3) - 高效使用内存

641 阅读3分钟

这是我参与8月更文挑战的第14天,活动详情查看:8月更文挑战

这是内存控制章节的第三篇文章,高效使用内存

前面两篇文章讲到,由于 V8 垃圾回收机制的限制,nodejs 在使用内存时有一些限制,只能使用部分内存(64位系统下约为1.4 GB,32位系统下约为0.7 GB),另外浅析了一个 V8 垃圾回收机制中,对于老生代和新生代的回收算法。

这篇文章讲在 V8 面前,我们开发时需要注意什么,才能让 V8 垃圾回收机制更高效的工作

作用域

触发垃圾回收第一个要讲的是作用域,JavaScript 中可以形成作用域的有:函数调用、with、全局作用域。

比如:

let foo = function() {
    let local = {};
}

foo 函数每次调用的时候都会创建对应的作用域,函数执行结束后,作用域将被销毁,同时局部变量 local 随作用域的销毁而销毁。被局部变量引用的对象的生命周期很短,会被分在新生代的 From 空间中,作用域释放后,引用的对象会在下一次垃圾回收时被释放

标识符查找

let bar = function () { 
    console.log(local);
};

比如这个例子,在执行 bar 函数时,会遇到 local 变量,首先会在当前作用域查找,如果查不到,会依次向上级的作用域查找,直到找到全局作用域,如果都找不到,会抛出一个未定义的错误。

变量的主动释放 如果是全局变量(不通过 var、let、const 声明,或者定义在 global 下的),要到进程退出后才会释放,会导致应用的对象常驻内存(老生代中)。

如果需要释放内存,需要使用 delete 删除引用关系(不推荐,有可能干扰 V8 的优化),或者通过赋值的方式解除引用。

global.foo = "I am global object";
global.foo = undefined;  // or null;

闭包

刚刚我们说过,作用域链上的对象访问是向上查找的,外部不能向内部访问,比如下面这个例子就会报错的:

let foo = function () { 
    (function () {
        var local = "局部变量"; 
    }());
    console.log(local); 
};

让外部作用域访问内部作用域中变量的方法叫做闭包(closure)。这得益于高阶函数的特性:函数可以作为参数或返回值

let foo = function () {
    let bar = function () {
        let local = '123';
        return function () {
            return local;
        }
    }
    let baz = bar();
    console.log(baz())
}
foo();

正常来说 local 会随着 bar 的作用域销毁被回收,但是因为 bar 返回的是一个函数,函数中可以访问 local,所以外部函数可以通过这个函数访问 local。

闭包的问题是,一旦有变量引用了这个中间函数,这个中间函数不会被释放,中间函数所在的外层作用域也不会被释放,除非不再有引用,才会逐步释放

总结

闭包和全局变量会导致内存无法立即回收,由于 V8 的内存限制,要注意不要让这类变量无限制增加,因为它们会让老生代的对象增多。