前言
写这篇文章最主要的原因是昨天刷Quero时,看见了一个回答对我一直以来关于JavaScript内存分配上的认识进行了一个补错。再加上最近是考试周了,没有大把时间学习新的东西,因此对Node.js内存管理这块好好学一学,毕竟Node.js的内存管理还是挺重要的。所以综合上面两个原因,先整理一个JS的内存管理机制吧。
一、内存的生命周期
在JavaScript中,在对内存的使用上一般会经历一个过程,通常来说这个过程是由JavaScript引擎所控制的,而JavaScript引擎中最出名的当属于V8了,在V8的控制下内存一般会有如下的生命周期:
- 内存分配
- 内存使用
- 内存回收
二、内存分配
在没有对JavaScript的内存分配进行一个了解之前,我的脑海里只记得这么一句话,但具体在哪看的已经不可考了:在JavaScript中,所有的简单类型会被放在栈中,而所有的引用类型类型会被放在堆中,就像这样:
// 分配至栈中
var a = 1
var b = "srtian"
// 分配至堆中
var person = {
age:22,
name: "srtian"
}
这句话在我脑海里面已经有很长时间了,而且我一直认为它没有错(当然这种情况下的确没错)。但就在昨天,我在刷Quero时无意看见了一个回答指出了我对内存分配的认识是错误的:
比如下面这段代码:
var a = 1
var b = 2
var c = {}
var d = function() {
b = 3
var f = 4
}
按照我原本的认为,其中a,b,f应该存入栈中,而c,d这两个对象则应该存入堆中。但事实并不是这样那个的,那位大佬给的解释是这样的:
The 'a' and 'f' variables are stored on the stack because they're referred only in one depth of closures.
'c' and 'd' despite being 'local' are objects (in Javascript function is an object) so they're put on a heap.
And 'b' is non-local variable so it's also put on a heap.
简单来说,只要在闭包中对一个简单类型进行引用,那么这个简单那类型也会被存放在堆中,而在函数中声明的简单类型则会被存放在栈中,因为它在执行一次后就会被释放。详情可以看大佬们的回答。
三、内存使用
使用值的过程实际上是对分配内存进行读取与写入的操作。读取与写入可能是写入一个变量或者一个对象的属性值,甚至传递函数的参数。
四、内存回收
JavaScript具有自动垃圾回收机制,执行环境会负责管理代码执行过程中使用的内存。而这种垃圾收集机制的原理其实很简单,就是找出不在继续使用的变量,然后释放其占用的内存。在JavaScript中垃圾回收机制有两种:
- 标记计数法
- 引用计数法
4.1 标记计数法
JavaScript 中最常用的垃圾收集方式就是标记清除,大部分的浏览器都使用它。这个算法把“对象是否不再需要”简化定义为“对象是否可以获得”。
这个算法假定设置一个叫做根(root)的对象(在Javascript里,根是全局对象)。定期的,垃圾回收器将从根开始,找所有从根开始引用的对象,然后找这些对象引用的对象……从根开始,垃圾回收器将找到所有可以获得的对象和所有不能获得的对象。
从2012年起,所有现代浏览器都使用了标记-清除垃圾回收算法,只是垃圾收集的时间间隔有所不同而已。所有对JavaScript垃圾回收算法的改进都是基于标记-清除算法的改进,并没有改进标记-清除算法本身和它对“对象是否不再需要”的简化定义。
4.2 引用计数法
引用计数法是一种不太常见的垃圾收集策略。所谓引用计数的含义就是跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型赋给该变量时,则这个值的引用次数就是1。如果同一个变量又被赋给另外一个变量,则该值的引用次数加1。相反,如果包含对着值引用的变量又取得了另一个值,则这个值的引用次数减1。
可以看看MDN上的示例:
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属性的那个对象现在也是零引用了
// 它可以被垃圾回收了
这种计数法很简单,但却有个限制:无法处理循环引用。在实际开发中,IE 6, 7 (现在已经不考虑他们了)使用引用计数方式对 DOM 对象进行垃圾回收。该方式常常造成对象被循环引用时内存发生泄漏:
var div;
window.onload = function(){
div = document.getElementById("myDivElement");
div.circularReference = div;
div.lotsOfData = new Array(10000).join("*");
};
参考资料:
MDN 内存管理
《JavaScript高级程序设计》
网友评论