js垃圾回收
内存生命周期
- 分配你所需要的内存
- 使用分配到的内存(读、写)
- 不需要时将其释放\归还
所有语言第二部分都是明确的.第一和第三部分在底层语言中是明确的,但在像 JavaScript 这些高级语言中,嵌入了'垃圾回收器',根据 Wiki 的定义,垃圾回收是一种自动的内存管理机制,用来追踪不用的内存并自动释放.
JavaScript 的内存分配
值的初始化
var n = 123; // 给数值变量分配内存
var s = "azerty"; // 给字符串分配内存
var o = {
a: 1,
b: null
}; // 给对象及其包含的值分配内存
// 给数组及其包含的值分配内存(就像对象一样)
var a = [1, null, "abra"];
function f(a){
return a + 2;
} // 给函数(可调用的对象)分配内存
// 函数表达式也能分配一个对象
someElement.addEventListener('click', function(){
someElement.style.backgroundColor = 'blue';
}, false);
通过函数调用分配内存
有些函数调用结果是分配对象内存:
var d = new Date(); // 分配一个 Date 对象
var e = document.createElement('div'); // 分配一个 DOM 元素
有些方法分配新变量或者新对象:
var s = "azerty";
var s2 = s.substr(0, 3); // s2 是一个新的字符串
// 因为字符串是不变量,
// JavaScript 可能决定不分配内存,
// 只是存储了 [0-3] 的范围.
var a = ["ouais ouais", "nan nan"];
var a2 = ["generation", "nan nan"];
var a3 = a.concat(a2);
// 新数组有四个元素,是 a 连接 a2 的结果
使用值
使用值的过程实际上是对分配内存进行读取与写入的操作.读取与写入可能是写入一个变量或者一个对象的属性值,甚至传递函数的参数.
垃圾回收
程序是运行在内存里的,当声明一个变量、定义一个函数时都会占用内存.内存的容量是有限的,如果变量、函数等只有产生没有消亡的过程,那迟早内存有被完全占用的时候.这个时候,不仅自己的程序无法正常运行,连其他程序也会受到影响.所以,在计算机中,我们需要垃圾回收.需要注意的是,定义中的“自动”的意思是语言可以帮助我们回收内存垃圾,但并不代表我们不用关心内存管理,如果操作失当,JavaScript 中依旧会出现内存溢出的情况.
当一些变量不在需要的时候,javascript 会对该这些变量占用的内存进行释放,而'哪些被分配的内存确实已经不再需要了'是一个难实现的任务.在基础语言中,往往要求开发人员来确定在程序中那一块内存不在需要它之后释放它.而高级语言解释器中嵌入了'垃圾回收器',它的主要工作是跟踪内存的分配和使用,以便当分配的内存不再使用时,自动释放它.垃圾回收实现只能有限制的解决一般问题,因为要知道是否仍然需要某块内存是无法判定的(无法通过某种算法解决).
引用
垃圾回收算法主要依赖于引用的概念.在内存管理的环境中,一个对象如果有访问另一个对象的权限(隐式或者显式),叫做一个对象引用另一个对象.例如,一个 Javascript 对象具有对它原型的引用(隐式引用)和对它属性的引用(显式引用).
在这里,“对象”的概念不仅特指 JavaScript 对象,还包括函数作用域(或者全局词法作用域).
变量
在 js 中有全局变量个局部变量(这里的全局变量是 global object,而 client 中 js 的全局对象指的是 window,也就是当前浏览器窗口).
全局变量在一个浏览器页面中始终是一只存在的.局部变量是在创建它的函数作用域中存在,当离开当前作用域的时候,会自动解除引用并进行释放.
引用计数垃圾收集
这是最初级的垃圾收集算法.此算法把“对象是否不再需要”简化定义为“对象有没有其他对象引用到它”.如果没有引用指向该对象(零引用),对象将被垃圾回收机制回收.
var o = {
a: {
b:2
}
};
// 两个对象被创建,一个作为另一个的属性被引用,另一个被分配给变量o
// 很显然,没有一个可以被垃圾收集
var o2 = o; // o2变量是第二个对“这个对象”的引用
o = 1; // 现在,“这个对象”的原始引用o被o2替换了
var oa = o2.a; // 引用“这个对象”的a属性
// 现在,“这个对象”有两个引用了,一个是o2,一个是oa
o2 = "yo"; // 最初的对象现在已经是零引用了
// 他可以被垃圾回收了
// 然而它的属性a的对象还在被oa引用,所以还不能回收
oa = null; // a属性的那个对象现在也是零引用了
// 它可以被垃圾回收了
限制:循环引用
该算法有个限制:无法处理循环引用的事例.在下面的例子中,两个对象被创建,并互相引用,形成了一个循环.它们被调用之后会离开函数作用域,所以它们已经没有用了,可以被回收了.然而,引用计数算法考虑到它们互相都有至少一次引用,所以它们不会被回收.
function f(){
var o = {};
var o2 = {};
o.a = o2; // o 引用 o2
o2.a = o; // o2 引用 o
return "azerty";
}
f();
标记清除算法
这个算法把“对象是否不再需要”简化定义为“对象是否可以获得”.
标记清除算法分为标记和清除两个阶段.这个算法假定设置一个叫做根(root)的对象(在 Javascript 里,根是全局对象).垃圾回收器将定期从根开始,找所有从根开始引用的对象,然后找这些对象引用的对象,然后对这些对象进行标记……从根开始,垃圾回收器将找到所有可以获得的对象和收集所有不能获得的对象.之后进入了清除阶段,标记的对象会与内存中的对象进行比较,然后清除内存中那些没有标记的对象
- 标记阶段,它将遍历堆中所有对象,并对存活的对象进行标记;
- 清除阶段,对未标记对象的空间进行回收.
这是 JavaScript 中最常见的垃圾回收方式.从 2012 年起,所有现代浏览器都使用了标记-清除的垃圾回收方法,低版本 IE 采用的是引用计数方法.
标记整理算法
Mark-Compact (标记整理)算法正是为了解决标记清除所带来的内存碎片的问题,提高对内存的利用.标记整理在标记清除的基础进行修改,标记阶段与标记清除相同,但是对未标记的对象处理方式不同,将其的清除阶段变为紧缩极端.与标记清除是对未标记的对象立即进行回收,标记整理则将活着的对象向内存区的一段移动,移动完成后直接清理掉边界外的内存.紧缩过程涉及对象的移动,所以效率并不是太好,但是能保证不会生成内存碎片.