js内存回收

概念:html

两种类型的泄露:node

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

js中堆和栈chrome

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

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

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

js对象:post

本地对象:(object  array  function)优化

宿主对象:(dom bom)this

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

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

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

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

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

一、标记清除

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

 

二、引用计数

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}被释放了。
 

 

三、内存泄漏常见的状况

1、意外的全局变量

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

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

function foo() {
    this.variable = "...";
}

// 没有对象调用foo, 也没有给它绑定this, 因此this是window
foo();

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

2、闭包引发的内存泄漏

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中就能够遍历进行删除管理事件

3、没有清理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还保留在内存里面。

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

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

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

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

5、子元素存在引用引发的内存泄漏

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内存释放那点事

相关文章
相关标签/搜索