最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 【译】内存管理(Memory Management)

    正文概述 掘金(小志Chris)   2021-02-08   547

    前言

    原文来自MDN JavaScript主题的高阶教程部分,一共5篇。分别涉及继承与原型、严格模式、类型数组、内存管理、并发模型和事件循环。本篇是第4篇,关于内存管理。


    下面是正文部分:

    一些底层语言例如 C,拥有手动的内存管理指令,如malloc()free()。相比之下,JavaScript 在对象创建时自动分配内存并且在它们不再使用时释放内存(垃圾回收机制(garbage collection))。这种机制会使人困惑:它让开发者觉得可以不必担心内存管理。

    内存生命周期

    任何编程语言的内存生命周期都几乎相同:

    1. 分配所需内存
    2. 使用分配的内存(读和写)
    3. 不再需要时释放该部分内存

    在所有编程语言中,第二点是明确的。而第一点和第三点,在底层语言中也是明确的,不过在高阶语言例如 JavaScript 中,多数是隐式的。

    在 JavaScript 中分配内存

    初始化值

    为了使开发者无需关注内存分配,初始化变量时,JavaScript 会自动分配一片内存。

    var n = 123; // 为一个数字分配内存
    var s = "azerty"; // 为一个字符串分配内存
    
    var o = {
      a: 1,
      b: null,
    }; // 为一个对象和其属性分配内存
    
    // (类对象) 为数组和其包含的值分配内存
    var a = [1, null, "abra"];
    
    function f(a) {
      return a + 2;
    } // 为一个函数分配内存 (函数是一个可调用的对象)
    
    // 为函数表达式分配内存(函数表达式也是对象)
    someElement.addEventListener(
      "click",
      function () {
        someElement.style.backgroundColor = "blue";
      },
      false
    );
    

    通过函数调用分配内存

    一些函数调用涉及到对象内存分配。

    var d = new Date(); // 为一个日期对象分配内存
    
    var e = document.createElement("div"); // 为一个DOM元素对象分配内存
    

    一些方法调用涉及到为原始值或对象分配内存。

    var s = "azerty";
    var s2 = s.substr(0, 3); // s2 是一个新字符串
    // 由于字符串 s 是不可变的值,
    // JavaScript 不会继续为 s2 分配内存,
    // 它仅仅保存下标 0-3 以获取子串.
    
    var a = ["ouais ouais", "nan nan"];
    var a2 = ["generation", "nan nan"];
    var a3 = a.concat(a2);
    // 拥有4个元素的新数组 a3 由 a 和 a2 合并而成
    

    使用已分配内存的变量

    使用变量意味着对分配的内存进行读和写。例如读取一个变量(或者对象属性)的值,为其赋值,或者是函数调用时传入参数。

    当内存不再使用时对其进行释放

    大部分内存管理的问题都发生在这个阶段。而其中最困难的就是判断何时不再需要这片内存。

    低层次的语言要求开发者在程序中自行判断,并进行内存释放。

    一些高阶语言,例如 JavaScript,利用了一种自动内存管理的形式,称作垃圾回收机制(garbage collection)(GC)。垃圾回收器的目的是监测内存分配,并确定何时该块内存不再需要,然后回收它。这个自动过程是一个近似过程,因为一块特定内存是否仍然需要是不确定的(undecidable)

    垃圾回收机制

    如上所述,一块已分配内存是否“不再需要”是不确定的。因此,垃圾回收器只能限制性地解决一般性问题。这个部分将会解释几个概念,它们有助于理解主要的垃圾回收算法以及它们各自的局限性。

    引用(References)

    垃圾回收算法主要依靠的一个概念叫做引用(reference)。在内存管理中,一个对象是否引用了另一个对象,取决于前一个对象是否访问了另一个(显式或隐式访问)。例如,一个 JavaScript 对象引用了它的原型对象(prototype)(隐式引用)和它自身的属性(显示引用)。

    在这里,“对象”的概念不仅指 JavaScript 对象,也包含了函数作用域(或全局作用域)。

    基于引用计数的垃圾回收机制

    这是最初级的垃圾回收算法。这种算法将判定“该对象是否仍然需要”简化为“是否仍有其他对象引用了该对象”。一个对象被称为“垃圾”,或者说可回收的标准是对它的引用数为 0。

    举例

    var x = {
      a: {
        b: 2,
      },
    };
    // 创建了两个对象。其中一个被另一个通过自身属性引用
    // 另一个则通过分配给变量 'x' 被引用
    // 显然,这俩都无法回收
    
    var y = x; // 变量 'y' 是第二个对象的第二次引用
    
    x = 1; // 现在,被变量 'x' 引用的对象只被变量 'y' 引用了
    
    var z = y.a; // 变量 'z' 引用了 属性 'a' 引用的对象
    //   这个对象现在拥有两处引用:一个是属性 'a',
    //   另一个是变量 'z'
    
    y = "mozilla"; //  最初被变量 'x' 引用的对象现在没有了引用,因此可以进行回收
    //   然而其属性 'a' 对应的对象仍然被变量 'z' 引用,
    //   因此该对象暂时无法回收
    
    z = null; // 最初变量 'x' 引用的对象属性 'a' 对应的对象没有了引用
    //   可以被回收
    

    限制:循环引用

    如果遇到循环引用,上述的回收机制会受限。下面例子中,创建了两个对象,他们同时被各自的属性引用,造成了循环。函数调用完成后,作用域消失,它们本应该被回收。然而引用计数算法不会认为他们可被回收,因为他们都至少存在一个引用。引用计数通常会导致内存泄漏。

    function f() {
      var x = {};
      var y = {};
      x.a = y; // x 引用了 y
      y.a = x; // y 也引用了 x
    
      return "azerty";
    }
    
    f();
    

    真实案例

    IE6 和 IE7 在 DOM 对象中使用了引用计数策略的垃圾回收器,循环引用是一个导致内存泄漏的常见错误:

    var div;
    window.onload = function () {
      div = document.getElementById("myDivElement");
      div.circularReference = div;
      div.lotsOfData = new Array(10000).join("*");
    };
    

    上面的例子中,名为"myDivElement"的 DOM 元素通过属性”circularReference“循环引用了自身。如果该属性没有被移除或置为null,基于引用计数的垃圾回收器将会认为该 DOM 对象至少存在一个引用,即使 DOM 节点从 DOM 树中移除,仍会占用内存。如果 DOM 元素含有大量数据(上面例子中的lotsOfData属性),那么存储这部分数据的内存将不会被释放,可能会出现内存有关的问题,例如浏览器变得十分卡顿。

    标记清除算法

    这种算法将判断”该对象是否不再需要“简化为”该对象是否不再能够被访问(an object is unreachable)“。

    这种算法假设有一组对象称为”根(Roots)“。JavaScript 中,根就是全局对象。垃圾回收器将定期从根对象开始,寻找所有被根对象引用的对象,然后被这些对象引用的类型,以此类推。在这个过程中,垃圾回收器会找到所有可被访问(reachable)的对象,且回收所有不再能够被访问的对象。

    这个算法是对前一个的改进,因为当一个对象有 0 个引用时,可以说该对象也是不再能被访问的。但是反之不成立,例如上面提到过的循环引用。

    截止 2012 年,所有现代浏览器都使用了基于标记清除算法的垃圾回收器。在过去几年里,针对 JavaScript 垃圾回收领域的改进都是基于该算法实现上的改进,而没有改变该算法本身,也没有改变”该对象是否不再需要“的简化定义。

    循环引用不再是一个问题

    在上述的第一个例子中,在函数调用返回后,从全局对象来看,变量xy指向的对象无法被任何对象访问。因此,它们会被垃圾回收器找到,占用的内存会被回收。

    限制:手动释放内存

    有时候,手动决定何时释放以及释放多少内存会更方便。为了释放一个对象占用的内存,这个对象需要被显示地标记为”不可访问“的。

    截止到 2019 年,在 JavaScript 中,暂无方法显式地或以编程的方式触发垃圾回收。

    Node.js

    Node.js 环境中提供了额外的配置项以及工具,可以用来排除一些内存问题。而在浏览器环境中执行的 JavaScript 可能无法使用这些配置项和工具。

    V8 引擎相关的命令行配置

    可以通过如下命令来增加最大可供使用的堆内存:

    node --max-old-space-size=6000 index.js

    我们也可以结合 Chrome Debugger,并使用如下命令来暴露垃圾回收器,从而排查一些内存有关的问题:

    node --expose-gc --inspect index.js
    

    参考

    • IBM article on "Memory leak patterns in JavaScript" (2007)
    • Kangax article on how to register event handler and avoid memory leaks (2010)
    • Performance

    起源地下载网 » 【译】内存管理(Memory Management)

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元