垃圾回收
JavaScript是使用垃圾回收的语言,也就是说执行环境负责在代码执行时管理内存。JavaScript通过自动内存管理实现内存分配和闲置资源回收。
基本思路
确定哪个变量不会再使用,然后释放它占用的内存。这个过程是周期性的,即垃圾回收程序每隔一定事件(或者说在代码执行过程中某个预定的手机时间)就会自动运行。垃圾回收过程是一个近似且不完美的方案,因为某块内存是否还有用,属于“不可判定的”问题,意味着靠算法是解决不了的。
在浏览器的发展史上,用到过两种主要的标记策略:标记清理和引用计数。
标记清理
JavaScript最常用的垃圾回收策略是标记清理(mark-and-sweep)。当变量进入上下文,比如在函数内部声明一个变量时,这个变量会被加上存在于上下文中的标记。而不在上下文中的变量,逻辑上讲,永远不应该释放她们的内存,因为只要上下文中的代码在运行,就有可能用到它们。当变量离开上下文时,也会被加上离开上下文的标记。
给变量加标记的方式有很多种。比如,当变量进入上下文时,反转某一位;或者可以维护“在上下文中”和“不在上下文中”两个变量列表,可以把变量从一个列表转移到另一个列表。标记过程的实现并不重要,关键是策略。
垃圾回收程序运行的时候,会标记内存中存储的所有变量(记住,标记方法有很多种)。然后,他会将所有在上下文中的变量,以及被上下文中变量引用的变量标记去掉。在此之后再被加上标记的变量就是待删除的了,原因是任何在上下文中的变量都访问不到它们了。随后垃圾回收程序做一次内存清理,销毁带标记的所有值并收回它们的内存。
引用技数
另一个没那么常用的垃圾回收策略是引用计数(reference counting)。其思路是对每个值都记录它被引用的次数。声明变量并给他赋一个引用值时,这个值的引用数为1.如同一个值又被赋给另一个变量,那么引用数减1。当一个值的引用数为0时,就说明没办法再访问到这个值了,因此可以安全地收回其内存了。垃圾回收程序下次运行的时候就会释放引用次数为0的值的内存。
ie9把BOM和DOM对象都改成了JavaScript对象,这同时也避免了由于存在两套垃圾回收算法而导致的问题,还消除了常见的内存泄露现象。
性能
垃圾回收程序会周期性运行,如果内存中分配了很多变量,则可能造成性能损失,因此垃圾回收的时间调度很重要。尤其是再内存有限的移动设备上,垃圾回收有可能会明显拖慢渲染的速度和帧速率。开发者不知道什么时候运行时会收集垃圾,因此最好的办法是再写代码时就要做到:无论什么时候开始收集垃圾,都能让它尽快结束工作。
现代垃圾回收程序会基于对Javascript运行时环境的探测来决定何时运行。探测机制因引擎而异,但基本上都是根据分配对象的大小和数量来判断。比如,根据V8的堆增长策略会根据活跃对象的数量外加一些余量来确定何时再次垃圾回收。
由于调度垃圾回收程序方面的问题会导致性能下降,IE曾饱受诟病。它的策略是根据分配数,比如分配了256个数量、4096个对象/数组字面量和数组槽位(slot),或者54KB字符串。只要满足其中某个条件,垃圾回收程序就会运行。这样实现的问题在于,分配那么多的变量的脚本,很可能再其整个生命周期内需要那么多变量,结果就会导致垃圾回收程序过于频繁地运行。由于对性能的严重影响,IE7最终更新了垃圾回收程序。
IE7发布后,JavaScript引擎的垃圾回收程序被调优为动态改变分配变量、字面量或数组槽位等触发垃圾回收的阈值。IE7的起始阈值都与IE6的相同。如果垃圾回收程序回收的内存不到已分配的15%,这些变量、字面量或数组槽位的阈值就会翻倍。如果有一次回收的内存达到已分配的85%,则阈值重置为默认值。这么一个简单的修改,极大地提升了重度依赖JavaScript的网页在浏览器中的性能。