Home > Article > Web Front-end > Detailed analysis of JavaScript memory allocation and management mechanism_Basic knowledge
你可能听说过JAVA、.NET、PHP这些语言有垃圾回收的内存管理机制,但是很少会听到JavaScript也有自己的内存管理机制,JavaScript同样有着类似的垃圾回收功能。本文主要讲述了JavaScript的垃圾回收原理和具体的过程。
简介
在底层语言中,比如C,有专门的内存管理机制,比如malloc() 和 free()。而Javascript是有垃圾回收(garbage collection)机制的,也就是说JS解释器会自动分配和回收内存。这样就有人觉得,我用的是高级语言,就不用关心内存管理了,其实这是不对的。
内存的生命周期
尽管语言不尽相同,而每种语言中内存的生命周期都是相似的:
1.当需要的时候分配内存
2.对内存进行读写操作
3.当上面分配的内存不再需要的时候,将他们释放掉
对于1,2两步,几乎所有语言操作起来都是明确地或者说很直观,没什么好说的。而在像Javascript一样的高级语言中,第三步操作就显得不那么直观。
Javascript中分配内存空间
变量初始化
当变量初始化的时候,Javascript会自动分配相应的内存空间(注:这里MDN上关于这里用的是Value initialization,到底是声明,还是在赋值时候分配空间,还要再学习一下)
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'; //个人补充,未考证,这里会为someElement分配空间,如注释所说,为对象分配空间
}, false);
函数调用时候分配空间
有的函数调用,会产生上面说的那种 为对象分配空间
var d = new Date();
var e = document.createElement('div'); // allocates an DOM element还有下面这种
var s = “azerty”;
var s2 = s.substr(0, 3); // s2 is a new string
// 由于Javascript中字符串是不可变的,所以Javascript也许并没有为s2中的字符串分配新空间,而是只存了[0, 3]的区间(用来索引)
var a = ["ouais ouais", "nan nan"];
var a2 = ["generation", "nan nan"];
var a3 = a.concat(a2); // 新的空间来存储数组a3
操作变量值
没什么好说的,读、写、函数调用。
内存不再被使用时,将它们释放掉
许多内存管理机制的问题都出现在这里。最麻烦的问题是确认“这块内存空间已经不需要了”。这往往需要程序员告知,这个程序中,这块内存已经不需要了,你们回收吧。
而高级语言解释器中嵌入了一个叫做“垃圾回收(garbage collector)”的工具,用来跟踪内存分配和使用情况,以便在它们不需要的时候将其自动回收。然而有个问题,一块内存空间是不是还有用,是具有不确定性的,也就是说,这个是没法用算法精确算出来的。
垃圾回收
如上所述原因,垃圾回收机制采取了一种有限的解决方案来处理上面的不确定性问题。下面介绍集中垃圾回收算法的思想以及相应的局限:
引用
这种方法,用到了一种引用的思想。当a能访问A时,就说A引用了a(不论是直接还是间接的)。比如,一个Javascript对象会引用他的原型(间接引用)和它的各个属性(直接引用)。
这种情形下,对象就被扩展的更广义了,在原生对象的基础上,还包含了函数的作用域链(或者全局的词法作用域)。
引用计数
这种方法是最拿衣服(naive)的垃圾回收算法。它把“可以回收”的标准定义为“没有其他人引用这个对象”(原文:This algorithm reduces the definition of “an object is not needed anymore” to “an object has no other object referencing to it”)。也就是说,只有当对象没有被引用的时候,才会被当作垃圾回收掉。
举个例子
var o = { // 称之为外层对象
a: { //称之为内层对象
b:2
}
}; // 创建了两个对象 内层对象作为外层对象的属性而被引用
// 而外层对象被变量o引用
// 显然,没有人会被垃圾回收
var o2 = o; // o2 also refers to the outer object mentioned above. Fortunately, now the reference count of the outer object is '2' (referenced by o and o2)
o = 1; // Now o no longer refers to the outer object, only o2 is referencing, and the reference count is '1'
var oa = o2.a; // oa refers to the inner object
// Now the inner object is referenced both as a property of the outer object and by oa, and the reference count is '2'
o2 = "yo"; // Okay, now o2 no longer refers to the outer object, and the outer object reference count is "0"
// This means that the outer object can be "garbage collected"
// However, the inner object is still referenced by oa, so it has not been recycled (personal note: there is a hint of closure here)
oa = null; // Now oa does not refer to the inner object
// The inner object is also garbage collected
Limitation: circular reference
Look at the code below:
function f(){
var o = {};
var o2 = {};
o.a = o2; // o references o2
o2.a = o; // o2 Quoteo
return “azerty”;
}
f();
// o o2 The two objects form a circular reference
// When the function is executed, they are locked in the scope of f, and no one outside can Use them
// So it stands to reason that they have no existing value and need to be garbage collected and release memory
// However, their reference counts are not "0"
// So here Under this reference counting mechanism, they are not recycled
Actual example
In IE6 and 7 versions of the browser, the reference counting mechanism is used. Therefore, the following code can reliably cause memory leaks in IE6 and 7
var div = document.createElement("div");
div.onclick = function(){
doSomething();
}; // The onclick attribute of div will reference function
// However, this function in turn refers to this div, because the div is within the scope of the handler.
// Causes the above circular reference, leading to memory leaks. Mark and clear algorithm
This algorithm defines "recyclable" as "object unreachable", that is, it cannot be accessed.
This algorithm will define a "root" and periodically start from the "root" to find all the objects under the "root" to see if it can find a path from the "root" to reference this object. Starting from different "roots", the garbage collection program can distinguish whether all objects are "unreachable". When the objects are "unreachable", they will be recycled.
This algorithm is better than the reference counting algorithm. Because "the reference count of an object is 0" can infer "this object is unreachable", and the converse proposition is false. In other words, this algorithm expands the scope of garbage collection.
Circular references are no longer a problem
In the above circular reference example, when the function returns, both o and o2 are no longer referenced by anyone, that is, they are "unreachable" , and it was naturally collected by the garbage.
Limitations: The object needs to be explicitly "unreachable"
Although it is a limitation, this situation rarely happens in practice, so few people pay attention to this.