介绍
低层次的语言,如C,具有低级别的内存管理命令,如:malloc()和free(),需要开发者手工释放内存。然而像javascript这样的高级语言情况则不同,对象(objects, strings 等)创建的时候分配内存,当他们不在使用的时候内存会被自动回收,这个自动回收的过程被称为垃圾回收。因为垃圾回收的存在,让javascript等高级语言开发者产生了一个错误的认识,以为可以不用关心内存管理。
内存生命周期
不管什么样的编程语言,内存的生命周期基本上是一致的。
1.分配你需要的内存
2.使用他进行读写操作
3.当内存不需要的时候,释放资源
步骤1和步骤2对于所有语言都一样,能明显觉察到。至于步骤3,低级别语言需要开发者显式执行。而对于像javascript这样的高级语言,这部分操作是交给解析器完成的,所以你不会觉察到。
javascript中的分配操作
值的初始化
在为变量赋值的时候,javascript会完成内存的分配工作。
一些函数当执行完毕之后,同样存在对象分配的情况发生。
对值的使用,其实也就是对分配后的内存执行读写操作。这些操作包括:对变量或者对象的属性进行读写操作,或者向函数传递参数。
当不再需要的时候,释放内存
绝大多数内存管理的问题都发生在这个阶段。最难做的事情是,如何判定分配的内存不再需要。这往往需要开发者做出判定,程序在什么时候不再需要内存,并释放他所占资源。
高级语言的解析器中嵌入了一个叫做“垃圾收集器”的程序,他的工作是用来跟踪内存的分配和使用,判定内存是否被需要,在不再需要的时候执行资源释放操作。他只能获得一个近似值,因为判断一个内存是否被需要,这是个不确定的问题(不能通过一种算法解决)。
垃圾回收
正如上文所述,我们无法准确的做到自动判定“内存不再需要”。所以,垃圾回收对该问题的解决方案有局限性。本节将解释必要的概念,了解主要的垃圾收集算法和它们的局限性。
引用
垃圾回收中一个主要的概念是引用。在内存管理中,当一个对象无论是显式的还是隐式的使用了另外一个对象,我们就说他引用了另外一个对象。例如,javascript对象存在一个隐式的指向原型的引用,还有显式指向他的属性值的引用。
在这里,对象的概念超出了javascript传统意义上对象的概念,他还包括函数作用域和全局作用域。
使用引用计数算法的垃圾回收
下面要介绍的是一种最理想化的算法,引入了 “对象不再需要” 和 “没有其他对象引用该对象” 的概念。当该对象的引用指针变为0的时候,就认为他可以被回收。
例子:
oa = null; // 现在属性a也不再被别的对象引用,该对象可以被回收了
该算法有其局限性,当一个对象引用另外一个对象,当形成循环引用时,即时他们不再被需要了,垃圾收集器也不会回收他们。
他引入了“对象不再需要”和“对象不可访问(对象不可达)”的概念。该算法假设有一系列的根对象(javascript中的根对象就是全局对象),每隔一段时间,垃圾收集器就会从根对象开始,遍历所以他引用的对象,然后再遍历引用对象引用的对象,以此类推。使用这种方式,垃圾收集器可以获得所有可访问的对象,回收那些不可访问的对象。
这种算法比之前的算法好些,0引用的对象会被设置为不可访问对象,同时他也避免了循环引用造成的困恼。
截止2012年,大多数现代浏览器使用的是这种“标记-清除算法”的垃圾回收器。JavaScript垃圾收集领域(代/增量/并发/并行的垃圾收集),在过去的几年改善了与之相关的算法,但是垃圾收集算法本身(标记-清除算法)和“如何判定一个对象不再需要”并没有得以改善。
周期不再是一个问题
在第一个例子中,函数调用结束之后,这两个对象不会被全局对象引用,也不会被全局对象引用的对象引用。因此,他们会被javascript垃圾回收器标记为不可访问对象。这种事情同样也发生在第二个例子中,当div和事件处理函数被垃圾回收器标记为不可访问,他们就会被释放掉。
限制:对象需要明确的标记为不可访问
这种标记的方法存在局限,但是我们在编程中被没有接触到他,所以我们很少关心垃圾回收相关的内容。