介绍
低级语言,像C语言具有能够进行内存管理的工具,比如malloc( )和free( ),另一方面,当一个变量被创建时,javascript的内存会自动分配,当变量不再使用时,也会自动释放。后面的这一过程被称为垃圾回收机制。这种垃圾回收机制是混乱管理的缘由,这给js(或其他高级语言)开发者带来他们能够不去关心内存是如何管理的感觉。这是错误的。
内存的生命周期
不论哪种编程语言,内存的生命周期都是相同的:
- Allocate the memory you need 分配你需要的内存
- Use the allocated memory (read, write) 使用分配的内存(读和写)
- Release the allocated memory when it is not needed anymore 当你不需要时释放内存
第一二部分在所以语言都是显式的,最后一部分是在低级语言中是显式的,但是在高级语言中却是隐式的,比如JavaScript。
javascript中的内存分配:
1、值得初始化,为了减轻程序员的编程负担,js在分配的时候就声明了值。
2、由函数返回来分配。
使用变量:
使用变量基本上就是读和写分配的内存。我们读写变量的值或者是一个对象的属性,甚至可以传递一个参数给函数来处理。
当不需要的时候释放内存:
大多数内存管理的问题都出现在这个时候,这里最难的就是找到不再需要这块内存的那个时间。通常这都是由开发人员来确定程序中的哪块内存不再需要,并释放它。
高级语言都嵌入了垃圾回收机制,其工作原理是跟踪内存分配和使用,以便找到何时不再需要一块已分配的内存,在这种情况下,它将自动释放。这个过程只能是模糊的,因为程序一般不知道一块内存是否还需要,这是不可判断的(这不能由算法解决)。
垃圾回收机制
如上所述,普遍问题是自动去查找一块分配的内存是否不再需要这是不可判断的。垃圾回收机制实现了如何解决这个问题的限制。本节将帮助理解垃圾回收机制的原理以及局限性。
垃圾回收机制的算法依赖的是引用原理(the notion of reference.)。在内存管理的环境中,一个对象被看做是另一个对象的引用,如果前者对后者具有一个访问(显式或者隐式)。比如,js对象具有对其原型(隐式)和属性(显式)的引用。
这里所说的“对象”的概念扩展到比常规js对象更广泛,并且包含函数作用域(以及全局词法作用域)。
下面接受的是最原始的垃圾回收集算法。该算法将“一个对象不再被需要”定义为“一个对象没有引用它的其他对象”。如果一个对象有0个引用指向,则该对象被认为是可回收的。
var o = {
a: {
b:2
}
};
// 2 objects are created. One is referenced by the other as one of its properties.
// The other is referenced by virtue of being assigned to the 'o' variable.
// Obviously, none can be garbage-collected
var o2 = o; // the 'o2' variable is the second thing that
// has a reference to the object
o = 1; // now, the object that was originally in 'o' has a unique reference
// embodied by the 'o2' variable
var oa = o2.a; // reference to 'a' property of the object.
// This object has now 2 references: one as a property,
// the other as the 'oa' variable
o2 = "yo"; // The object that was originally in 'o' has now zero
// references to it. It can be garbage-collected.
// However what was its 'a' property is still referenced by
// the 'oa' variable, so it cannot be freed
oa = null; // what was the 'a' property of the object originally in o
// has zero references to it. It can be garbage collected.
限制:循环
当该算法涉及到循环时,就会受到限制,下面的事例中,创建了两个对象并且相互引用,从而构建了一个循环。函数调用完毕后,它们讲不会离开函数作用域,所以他们实际上是没有作用的并且应该被释放。然而引用计数算法认为每个对象都被引用了一次,因此没有可以被垃圾回收的对象。
function f(){
var o = {};
var o2 = {};
o.a = o2; // o references o2
o2.a = o; // o2 references o
return "azerty";
}
f();
真实的案例
在ie6、7中已知使用的是DOM对象的引用计数垃圾回收器。循环引用是一个常见的会产生内存泄漏的错误。
var div;
window.onload = function(){
div = document.getElementById("myDivElement");
div.circularReference = div;
div.lotsOfData = new Array(10000).join("*");
};
在上面的示例中,DOM元素“myDivElement”在“circularReference”属性中具有对其自身的循环引用。如果该属性未被明确删除或清空,引用计数的垃圾回收器将始终有一个完整引用,并且将DOM元素保留在内存中,即使它已从DOM树中删除。如果DOM元素保存大量数据(在上面的示例中使用“lotsOfData”属性),则此数据消耗的内存永远不会被释放。
标记和扫描算法
该算法将“对象不再需要”定义归纳为“对象不可达”。
该算法假定知道一组被称为roots的对象(在js中root就是全局对象)。垃圾回收器将定期从这些roots开始查找,然后找到所有从这些roots引用的对象,和所有不被他们引用的对象。从roots开始,垃圾回收器将找到的所有可到达得对象并回收不可到达的对象。
该算法比之前的算法更好,因为“对象零引用”所认为的该对象不可达,相反我们已经看到了循环就知道这不是真的。
截至2012年,所有现代浏览器都装有一个标记清除垃圾回收器。在过去几年中,在JavaScript垃圾回收领域generational/incremental/concurrent/parallel garbage collection)中所做的所有改进都是该算法的实现改进,而不是垃圾回收算法本身的概念, 当“一个对象不再需要”。
循环不再是问题
在上面的第一个例子中,在函数调用返回之后,2个对象不再被从全局对象可达的东西引用。因此,它们将被垃圾回收器发现不可达。
第二个例子也是一样。一旦div和它的处理程序从根无法访问,它们都可以被垃圾回收,尽管引用彼此。
限制:对象需要明确无法访问
虽然这被标记为一个限制,这是一个很少在实践中达到的,这就是为什么没有人经常关心垃圾回收的原因。