js内存回收
Posted 可爱的小熊
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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。
五、子元素存在引用引起的内存泄漏
- 黄色是指直接被 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)
这里以cocos egret为例:打开一个界面a拍下快照,多次切换界面后回到界面a再次拍下快照。选中第二个快照,点选summary选中comparison,chrome将自动比较,此时看delta这一栏,点击排序查看增量,结合construcor中查找你写的对应类与增量,进行排查!
参考:
以上是关于js内存回收的主要内容,如果未能解决你的问题,请参考以下文章