垃圾回收器是一把十足的双刃剑。其好处是可以大幅简化程序的内存管理代码,因为内存管理无需程序员来操作,由此也减少了(但没有根除)长时间运转的程序的内存泄漏。对于某些程序员来说,它甚至能够提升代码的性能。
另一方面,选择垃圾回收器也就意味着程序当中无法完全掌控内存,而这正是移动终端开发的症结。对于JavaScript,程序中没有任何内存管理的可能——ECMAScript标准中没有暴露任何垃圾回收器的接口。网页应用既没有办法管理内存,也没办法给垃圾回收器进行提示。
严格来讲,使用垃圾回收器的语言在性能上并不一定比不使用垃圾回收器的语言好或者差。在C语言中,分配和释放内存有可能是非常昂贵的操作,为了使分配的内存能够在将来释放,堆的管理会趋于复杂。而在托管内存的语言中,分配内存往往只是增加一个指针。但随后我们就会看到,当内存耗尽时,垃圾回收器介入回收所产生的巨大代价。一个未经琢磨的垃圾回收器,会致使程序在运行中出现长时间、无法预期的停顿,这直接影响到交互系统(特别是带有动画效果的)在使用上的体验。引用计数系统时常被吹捧为垃圾回收机制的替代品,但当大型子图中的最后一个对象的引用解除后,同样也会有无法预期的停顿。而且引用计数系统在频繁执行读取、改写、存储操作时,也会有可观的性能负担。
或好或坏,JavaScript需要一个垃圾回收器。V8的垃圾回收器实现现在已经成熟,其性能优异,停顿短暂,性能负担也非常可控。
基本概念
垃圾回收器要解决的最基本问题就是,辨别需要回收的内存。一旦辨别完毕,这些内存区域即可在未来的分配中重用,或者是返还给操作系统。一个对象当它不是处于活跃状态的时候它就死了(废话)。一个对象处于活跃状态,当且仅当它被一个根对象或另一个活跃对象指向。根对象被定义为处于活跃状态,是浏览器或V8所引用的对象。比如说,被局部变量所指向的对象属于根对象,因为它们的栈被视为根对象;全局对象属于根对象,因为它们始终可被访问;浏览器对象,如DOM元素,也属于根对象,尽管在某些场合下它们只是弱引用。
从侧面来说,上面的定义非常宽松。实际上我们可以说,当一个对象可被程序引用时,它就是活跃的。比如:
function f() { var obj = {x: 12}; g(); // 可能包含一个死循环 return obj.x; }
def scavenge(): swap(fromSpace, toSpace) allocationPtr = toSpace.bottom scanPtr = toSpace.bottom for i = 0..len(roots): root = roots[i] if inFromSpace(root): rootCopy = copyObject(&allocationPtr, root) setForwardingAddress(root, rootCopy) roots[i] = rootCopy while scanPtr < allocationPtr: obj = object at scanPtr scanPtr += size(obj) n = sizeInWords(obj) for i = 0..n: if isPointer(obj[i]) and not inOldSpace(obj[i]): fromNeighbor = obj[i] if hasForwardingAddress(fromNeighbor): toNeighbor = getForwardingAddress(fromNeighbor) else: toNeighbor = copyObject(&allocationPtr, fromNeighbor) setForwardingAddress(fromNeighbor, toNeighbor) obj[i] = toNeighbor def copyObject(*allocationPtr, object): copy = *allocationPtr *allocationPtr += size(object) memcpy(copy, object, size(object)) return copy
内容版权声明:除非注明,否则皆为本站原创文章。