javascript垃圾回收与内存泄漏
2018-02-02 本文已影响127人
Husbin
垃圾回收机制
自动垃圾收集机制
- javascript具有自动垃圾收集机制,也就是说,执行环境会负责管理代码执行过程中使用的内存。即开发人员不需要当心内存使用的问题,所需内存的分配以及无用内存的回收完全实现了自动管理。这种垃圾回收机制的原理:找出那些不再继续使用的变量,然后释放其内存。为此,垃圾收集器会按照固定的时间间隔,周期性地执行这一操作。
局部变量的生命周期
- 局部变量只在函数执行的过程中存在。在这个过程中,会为局部变量在栈内存或堆内存上分配相应的空间,以便存储他们的值。然后再函数中使用这些变量,直至函数执行结束。此时,局部变量就没有存在的必要了,因此可以释放他们的内存以供将来使用。
javascript中的2中垃圾回收机制
标记清除(Mark-and-sweep)
- 包括标记阶段和清除阶段。
-
标记阶段:垃圾回收器创建了一个
roots
列表。Roots
通常是代码中全局变量的引用。javascript中,window
对象是一个全局变量,被当作root
。window
对象总是存在,因此垃圾回收器可以检查他和他所有子对象是否存在(即不是垃圾)。所有的roots
被检查和标记为激活(即不是垃圾)。所有的子对象也被递归检查,检查从root
开始的所有对象,如果是可达的,则打上标记。标记阶段到此结束。 - 清除阶段:标记阶段完成时,被标记的对象被视为“存活”对象。然后重新进入递归扫描阶段,将没有标记的对象进行回收。这一阶段称为清除阶段。在扫描的同时,还需要将存活的对象的标记清除掉,以便下一次GC操作做准备。
引用计数(reference counting)
- 引用计数的含义是跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型赋给该变量时,则这个值的引用次数就是1。如果同一个值又被赋给另一个变量,则将该引用次数再加1。相反,如果包含对这个值得引用变量又取得了另一个值,则这个值的引用次数就减1。当这个值得引用次数变为0时,则说明没有办法在访问这个值了,因而久可以将其占用的内存空间回收回来。这样,垃圾回收器下次再运行时,会释放那些引用次数为0的值所占用的内存。
内存泄漏
- 内存泄漏是指应用程序使用过的内存片段,在不需要的时候,不能返回到操作系统或可用的内存池中的情况。javascript中的内存泄漏同理,当一个值不再需要被引用了,但却无法被垃圾回收器回收的情况,就是内存泄露。
javascript中3种常见的内存泄漏
1. 意外的全局变量
举个例子(普通变量):
function fun(){
globalVar = "this is a hidden global variable";
}
//在函数体内部定义变量,如果没有使用var声明,则定义的变量是全局变量,即成为window的一个属性
//而window总是存在,因此不会被回收
再举个例子(由this创建的变量):
function foo() {
this.variable = "potential accidental global";
console.log(this)
}
// Foo 调用自己,this 指向了全局对象(window)
// 而不是 undefined
foo();
image.png
-
解决方案:在js文件头部加上(ES5)
use strict
,使用严格模式,可以避免此类错误的发生。如果必须使用全局变量存储大量数据时,确保用完以后把它设置为 null 或者重新定义。
2.被遗忘的计数器或者回调函数
举个例子:
var someResource = getData();
setInterval(function() {
var node = document.getElementById('Node');
if(node) {
// 处理 node 和 someResource
node.innerHTML = JSON.stringify(someResource));
}
}, 1000);
//重复计数器并没有使用 clearInterval()终止,导致内存无法回收
- 对于观察者的例子,一旦它们不再需要(或者关联的对象变成不可达),明确地移除它们非常重要。老的 IE 6 是无法处理循环引用的。如今,即使没有明确移除它们,一旦观察者对象变成不可达,大部分浏览器是可以回收观察者处理函数的。
3. 脱离DOM的引用
- 有时,保存 DOM 节点内部数据结构很有用。假如你想快速更新表格的几行内容,把每一行 DOM 存成字典(JSON 键值对)或者数组很有意义。此时,同样的 DOM 元素存在两个引用:一个在 DOM 树中,另一个在字典中。将来你决定删除这些行时,需要把两个引用都清除。
var elements = {
button: document.getElementById('button'),
image: document.getElementById('image'),
text: document.getElementById('text')
};
function doStuff() {
image.src = 'http://some.url/image';
button.click();
console.log(text.innerHTML);
// 更多逻辑
}
function removeButton() {
// 按钮是 body 的后代元素
document.body.removeChild(document.getElementById('button'));
// 此时,仍旧存在一个全局的 #button 的引用
// elements 字典所引用的button 元素仍旧在内存中,不能被 GC 回收。
}
4.闭包(正常情况下不会造成内存泄漏)
由于IE9 之前的版本对JScript 对象和COM 对象使用不同的垃圾收集。因此闭包在IE 的这些版本中会导致一些特殊的问题。具体来说,如果闭包的作用域链中保存着一个HTML 元素,那么就意味着该元素将无法被销毁。
reference -- javascript高级程序设计第三版
举个例子:
function closure(){
var element = document.getElementById("someElement");
element.onclick = function(){
alert(element.id);
};
}
- 以上代码创建了一个作为element 元素事件处理程序的闭包,而这个闭包则又创建了一个循环引用。由于匿名函数保存了一个对closure()的活动对象的引用,因此就会导致无法减少element 的引用数。只要匿名函数存在,element 的引用数至少也是1,因此它所占用的内存就永远不会被回收。
解决方案:把element.id 的一个副本保存在一个变量中,从而消除闭包中该变量的循环引用同时将element变量设为null。
function closure(){
var element = document.getElementById("someElement");
var id = element.id;
element.onclick = function(){
alert(id);
};
element = null;
}
- 因此可以看出,闭包并不会引起内存泄漏,只是由于IE9之前的版本对JScript对象和COM对象使用不同的垃圾收集,从而导致内存无法进行回收,这是IE的问题,所以正常使用闭包的情况下不会导致内存泄漏。