• 内存泄漏问题总结


    1、内存的生命周期

      无论你使用那种语言,内存的生命周期基本是都差不多:分配内存 —— 使用内存 —— 释放内存,以下是生命周期中每一步发生了什么的一个概述:

      Allocate memory —— 操作系统分配内存,允许你的程序使用它。在基础语言中(例如 C ),这是一个开发者自己处理的明确操作。然而,在高级语言中,它已经为你处理了。

      Use memory —— 现在你就可以使用之前分配好的内存了。当你在代码中使用变量时,读 和 写 的操作正在发生。

      Release memory —— 现在该释放你不再需要的内存了,以便它们能够被再次使用。与分配内存的操作一样,这种操作在基础语言中是明确执行的。

    2、JavaScript 中的分配内存

      现在,我们将要解释 JavaScript 中第一步(分配内存)是如何工作的。

      JavaScript 解放了开发者处理内存分配的责任——JavaScript 自己在声明 values 时就做了这件事

    var n = 374; // allocates memory for a number
    var s = 'sessionstack'; // allocates memory for a string 
    var o = {
      a: 1,
      b: null
    }; // allocates memory for an object and its contained values
    var a = [1, null, 'str'];  // (like object) allocates memory for the
                               // array and its contained values
    function f(a) {
      return a + 3;
    } // allocates a function (which is a callable object)
     
    // function expressions also allocate an object
    someElement.addEventListener('click', function() {
      someElement.style.backgroundColor = 'blue';
    }, false);

      js中使用内存:基本上在 JavaScript 中使用内存的意思就是在内存在进行 读 和 写。这个操作可能是一个变量值的读取或写入,一个对象属性的读取或写入,甚至时向函数中传递参数。

      当内存不需要时释放内存:大多数的内存管理问题发生在这个阶段。这里最困难的任务就是确定内存何时就不再被需要了。它通常需要开发人员确定程序中哪里不再需要这样的内存,并释放它。

      高级语言拥有垃圾回收器,它的职责就是追踪内存分配和使用情况,找到不再被使用的内存,然后自动地释放它。不幸的是,这个过程只能得到一个近视的值,因为内存是否被需要是不可判定的(不能用算法求解)。大多数垃圾回收器通过判断内存是否能够被再次访问来工作的,例如:指向它的所有变量都超出了作用域。然而,这只能得到一个近似值。因为在任何位置,存储器位置可能仍然具有指向其范围的变量,但是它可能将永远不会被再次访问了。

    3、垃圾回收

      垃圾回收语言中的泄漏的主要原因是不必要的引用。垃圾回收算法依靠的主要概念就是引用。

      引用计数垃圾回收法:如果一个对象指向它的引用对象数为0,那么就该垃圾回收了

      循环依赖造成无法垃圾回收的情况

      标记扫描算法:解决循环依赖的问题

      即对象不可达,就垃圾回收

      该算法由以下步骤组成:

      (1)垃圾回收器构建“roots”列表。Roots 通常是代码中保留引用的全局变量。在 JavaScript 中,“window” 对象可以作为 root 全局变量示例。

      (2)所有的 roots 被检查并标记为 active(即不是垃圾)。所有的 children 也被递归检查。从 root 能够到达的一切都不被认为是垃圾。

      (3)所有为被标记为 active 的内存可以被认为是垃圾了。收集器限制可以释放这些内存并将其返回到操作系统。

      这个算法优于前一个,因为“一个对象零引用”会让这个对象不是可达的。反过来就不一定对了,因为存在循环引用。

    4、四种常见的内存泄漏

    (1)意外的全局变量以及显示的全局变量

      意外的全局变量,常见的就是:1、未声明的变量;2、误用this指针,比如下面

    function foo() {
        this.var1 = "potential accidental global";
    }
    foo();
    

      解决方案:

      1、为了防止这些错误的发生,可以在 JavaScript 文件开头添加 “use strict”,使用严格模式。这样在严格模式下解析 JavaScript 可以防止意外的全局变量。

      2、即使我们讨论了如何预防意外全局变量的产生,但是仍然会有很多代码用显示的方式去使用全局变量。这些全局变量是无法进行垃圾回收的(除非将它们赋值为 null 或重新进行分配)。特别是用来临时存储和处理大量信息的全局变量非常值得关注。如果你必须使用全局变量来存储大量数据,那么,请确保在使用完之后,对其赋值为 null 或者重新分配。

    (2)timers与callbacks

    var serverData = loadData();
    setInterval(function() {
        var renderer = document.getElementById('renderer');
        if(renderer) {
            renderer.innerHTML = JSON.stringify(serverData);
        }
    }, 5000); //This will be executed every ~5 seconds.

      这个例子阐述着 timers 可能发生的情况:计时器会引用不再需要的节点或数据。

      renderer 可能在将来会被移除,使得 interval 内的整个块都不再被需要。但是,interval handler 因为 interval 的存活,所以无法被回收(需要停止 interval,才能回收)。如果 interval handler 无法被回收,则它的依赖也不能被回收。这意味着 serverData——可能存储了大量数据,也不能被回收。在观察者模式下,重要的是在他们不再被需要的时候显式地去删除它们(或者让相关对象变为不可达)。

      还有一种定时器泄漏如下

    var val = 0;
    for (var i = 0; i < 90000; i++) {
      var buggyObject = {
        callAgain: function() {
          var ref = this;
          val = setTimeout(function() {
            ref.callAgain();
          }, 90000);
      }
    }
    buggyObject.callAgain();

      如果你想回收buggyObject,给它设为:buggyObject = null;   //虽然你想回收但是timer还在,所以还是回收不了

    //解决方法,先停止定时器
    clearTimeout(val);
    buggyObject = null;

      所以一定要是先清除子集的内存,再清除本集的内存,才可以垃圾回收。dom引用造成的泄漏也会出现这样的问题。

    (3)闭包导致的内存泄漏

    (4)dom引用

      有时候,在数据结构中存储 DOM 结构是有用的。假设要快速更新表中的几行内容。将每行 DOM 的引用存储在字典或数组中可能是有意义的。当这种情况发生时,就会保留同一 DOM 元素的两份引用:一个在 DOM 树种,另一个在字典中。如果将来某个时候你决定要删除这些行,则需要让两个引用都不可达。

      还有一个额外的考虑,当涉及 DOM 树内部或叶子节点的引用时,必须考虑这一点。假设你在 JavaScript 代码中保留了对 table 特定单元格(<td>)的引用。有一天,你决定从 DOM 中删除该 table,但扔保留着对该单元格的引用。直观地来看,可以假设 GC 将收集除了该单元格之外所有的内容。实际上,这不会发生的:该单元格是该 table 的子节点,并且 children 保持着对它们 parents 的引用。也就是说,在 JavaScript 代码中对单元格的引用会导致整个表都保留在内存中的。保留 DOM 元素的引用时,需要仔细考虑。

      当原有的DOM被移除时,子结点引用没有被移除则无法回收

    let select = document.querySelector;
    let treeRef = select('#tree');
    
    let leafRef = select('#leaf');   //在DOM树中leafRef是treeFre的一个子结点
    select('body').removeChild(treeRef);//#tree不能被回收入,因为treeRef还在
    
    // 解决方法
    treeRef = null;  //tree还不能被回收,因为叶子结果leafRef还在
    leafRef = null;  //现在#tree可以被释放了

      DOM 插入顺序导致内存泄漏

      当动态创建的 2 个不同范围的 DOM 对象附加到一起的时候,一个临时的对象会被创建。这个 DOM 对象改变范围到 document 时,那个临时对象就没用了,这个临时对象没有被回收将导致内存泄漏。如果我们将这两个DOM添加到原有的 DOM 对象上就不会产生中间临时对象。

  • 相关阅读:
    125、TensorFlow计算图的执行
    123、TensorFlow的Job
    122、TensorFlow多设备运行
    121、TensorFlow张量命名
    关于CSRF跨域请求伪造的解决办法
    angular基于ui-router实现系统权限控制
    gulp自动化打包及静态文件自动添加版本号
    深入研究HTML5实现图片压缩上传
    移动web模拟客户端实现多方框输入密码效果
    高级功能:很有用的javascript自定义事件
  • 原文地址:https://www.cnblogs.com/goloving/p/11135426.html
Copyright © 2020-2023  润新知