• js内存回收


    概念:

    两种类型的泄露:

    周期性的内存增长导致的泄露,以及偶现的内存泄露。显而易见,周期性的内存泄露很容易发现;偶现的泄露比较棘手,一般容易被忽视,偶尔发生一次可能被认为是优化问题,周期性发生的则被认为是必须解决的 bug。

    js中堆和栈

    栈:stack - 存放原始值(简单数据类型),连续的存储空间。栈空间小,读写快。

    堆:heap - 存放引用值(new arry object...),散列的存储空间。堆空间大,读写慢。

    例如:当我们用new实例化一个类的时候,这个new出来的对象就保存在heap里面,而这个对象的引用则存储在stack里。程序通过stack里的引用找到这个对象。例如var a = [1,2,3];,a是存储在stack里的引用,heap里存储着内容为[1,2,3]的Array对象

    js对象:

    本地对象:(object  array  function)

    宿主对象:(dom bom)

    本地对象之间使用标记清除,不会造成内存泄漏。

    本地和宿主对象之间使用引用计数,关联至当下cocos egret 引擎。(循环引用,闭包)

    总方针:在使用完毕后切断引用链,解除事件绑定。

    堆的内存释放由一特定算法的垃圾收集器进行(GC):标记清除 引用计数 复制算法

    本质:当一个对象无用的时候,即程序中无变量引用这个对象时,就会从内存中释放掉这个变量。  

    1、标记清除

    function test(){
      var a = 10;//被标记进入环境  
    }
    test();//执行结束后被标记离开环境 被回收

    2、引用计数

    function test(){
      var a = {}; //a的引用次数为0
      var b = a; //a的引用次数为1
      var c = a;//a的引用次数为2
      var b = {};  //a的引用次数减1 为 1 
    }

    当a 为零的时候,gc会将其回收销毁。

    注意:循环引用计数,相互引用将无法使用引用计数回收。

    function fn(){
      var a = {};
      var b ={};
      a.obj = b;
      b.obj = a;
    }
    fn();
    var element = document.getElementById(" ...");
    
    var myObj = new Object();
    
    myObj.e = element;
    
    element.o = myObj;

    这例子Dom对象element和本地对象myObj之间循环引用 

    简单描述:

      三个对象 A 、B 、C

         若A的某一属性引用着B,同样C也被B的属性引用着。如果将A清除,那么B、C也被释放。

         若这里增加了C的某一属性引用B对象,如果这是清除A,那么B、C不会被释放,因为B和C之间产生了循环引用。

     var a = {};
        a.pro = { a:100 };
        a.pro.pro = { b:100 };
        a = null ; 
        //这种情况下,{a:100}和{b:100}就同时也被释放了。
                
        var obj = {};
        obj.pro = { a : 100 };
        obj.pro.pro = { b : 200 };
        var two = obj.pro.pro;
        obj = null;    
        //这种情况下 {b:200}不会被释放掉,而{a:100}被释放了。
     

    3、内存泄漏常见的情况

    一、意外的全局变量

    function leaks(){
       leak ="xxx"; leak成为全局变量不会被回收  
    }

    说明:js中如果不用var声明变量,该变量将被视为window对象(全局对象)的属性,也就是全局变量.

    function foo() {
        this.variable = "...";
    }
    
    // 没有对象调用foo, 也没有给它绑定this, 所以this是window
    foo();

    方案:添加"use strict" 可避免。

    二、闭包引起的内存泄漏

    function bindEvent(){
      var  obj =document.createElement("xx");
      obj.click = function(){
       //....
      }
    }

    闭包可以维持函数内的局部变量,使其得不到释放。

    方案:将事件定义在外部, obj.click = this.clickFunction;  function clickFunction(){...}或者将其对象的引用删除obj.click = null;

    window.onunload = function(){
            var one = document.getElementById( 'xx' );
            one.click = null;
        };

    拓展:在cocos & egret中就可以遍历进行删除管理事件

    三、没有清理dom元素引用

    var element = {
      button: document.getElementById("button");
    }
    function shuff(){
      button.click();RemoveButton()
    }
    function RemoveButton(){ document.body.removeChild(document.getElementById("button")); }

    虽然 removeChild 移除了button,但element里还保留着对button的引用,则button还保留在内存里面。

    四、被遗忘的定时器或者回调

    var data = {};
    setInterval(function(){
       var node = document.getElementById("Node");
    if(node){
    node.innerHtml = JSON.stringify(data);
    }
    ...},
    1000)

    如果id为Node的元素从Dom中移除,该定时器仍会存在,同时回调函数对data的引用,定时器外的data也无法释放。

    方案:清除定时器。如果有引用变量同时设为null。

    五、子元素存在引用引起的内存泄漏

    code

    • 黄色是指直接被 js变量所引用,在内存里
    • 红色是指间接被 js变量所引用,如上图,refB 被 refA 间接引用,导致即使 refB 变量被清空,也是不会被回收的
    • 子元素 refB 由于 parentNode 的间接引用,只要它不被删除,它所有的父元素(图中红色部分)都不会被删除 

    方案:纯粹refA = null 无效,需要refA = null ; refB =null;

    var select = document.querySelector;
    var treeRef = select('#tree');
    
    var leafRef = select('#leaf');   //在COM树中leafRef是treeFre的一个子结点
    
    select('body').removeChild(treeRef);//#tree不能被回收入,因为treeRef还在

    方案:

    treeRef = null;//tree还不能被回收,因为叶子结果leafRef还在
    leafRef = null;//现在#tree可以被释放了

     使用chrome查看泄漏

    Heap Profiling可以记录当前的堆内存(heap)快照,并生成对象的描述文件,该描述文件给出了当时JS运行所用到的所有对象,以及这些对象所占用的内存大小、引用的层级关系等等。(使用快照会自动执行一次gc

    列字段解释:
    Constructor -- 类名Distance -- 估计是对象到根的引用层级距离
    Objects Count -- 给出了当前有多少个该类的对象
    Shallow Size -- 对象所占内存(不包含内部引用的其它对象所占的内存)(单位:字节)
    Retained Size -- 对象所占总内存(包含内部引用的其它对象所占的内存)(单位:字节)

    这里以cocos egret为例:打开一个界面a拍下快照,多次切换界面后回到界面a再次拍下快照。选中第二个快照,点选summary选中comparison,chrome将自动比较,此时看delta这一栏,点击排序查看增量,结合construcor中查找你写的对应类与增量,进行排查!

    参考:

    一个意想不到的Javascript内存泄漏

    JavaScript 内存泄漏教程

    JS内存泄漏排查方法-Chrome Profiles

    谈一谈Javascript内存释放那点事

  • 相关阅读:
    从温设计模式
    php pdf转图片
    PHP 微服务集群搭建
    死磕nginx系列--nginx 限流配置
    分别
    一生悲哀
    三十男人的思考
    test markdown
    linux 系统内核空间与用户空间通信的实现与分析<转>
    ue4 SNew补遗
  • 原文地址:https://www.cnblogs.com/darrenhwang/p/7498603.html
Copyright © 2020-2023  润新知