浏览器的垃圾回收机制经历了多个阶段的演变。
在早期,垃圾回收机制主要是引用计数。引用计数的基本思想是跟踪每个值的引用次数,当声明一个变量并将引用类型的值赋给该变量时,这个值的引用次数就是1。如果同一个值又被赋给另一个变量,则该值的引用次数加1。相反,如果包含对这个值引用的变量又取得另外一个值,则这个值的引用次数减1。当这个值的引用次数变成0时,就说明没有办法访问这个值了,因此就可以将其占用的内存空间回收回来。然而,引用计数无法处理循环引用的情况,即两个或更多的对象互相引用,即使它们在其他地方都没有被引用,它们的引用计数也不会为0,因此无法被回收,可能会导致内存泄漏。
为了解决引用计数的问题,标记清除(mark-sweep)算法被引入浏览器垃圾回收机制中。标记清除算法从根对象开始,遍历程序的整个“对象图谱”,将所有可达的对象标记为活动的,所有不可达的对象则被清除。标记清除算法可以解决循环引用的问题,只要这些对象都是不可达的,就可以被清除。然而,标记清除算法需要遍历整个对象图谱,因此其效率可能会受到一定影响。
为了提高垃圾回收的效率,增量标记(incremental marking)和并发标记(concurrent marking)等改进算法被引入浏览器垃圾回收机制中。增量标记将标记工作分解为更小的模块,可以让JS应用逻辑在模块间隙执行一会,从而不至于让应用出现停顿情况。并发标记则是在垃圾回收过程中允许其他线程同时运行,从而减少垃圾回收对应用性能的影响。
此外,浏览器实现标识无用变量的策略也随着时间的推移不断改进。除了引用计数和标记清除算法外,还有标记清除与复制(mark-compact)算法、增量式标记清除(incremental mark-compact)算法等。这些算法都在不断地优化和完善垃圾回收机制,以更好地平衡内存使用和程序性能。
总的来说,浏览器的垃圾回收机制经历了多个阶段的演变和优化,从早期的引用计数到现在的增量式标记清除和并发标记等算法,都在不断地提高垃圾回收的效率和性能。
V8的垃圾回收策略主要基于分代式回收机制。
根据对象的存活时间,V8将内存分为新生代和老生代两块内存空间。新建的对象会被优先分配到新生代内存中,而对象存活时间较短的也被分配到新生代内存中,将对象存活时间较长的分配到老生代内存中。
对于新生代的垃圾回收,V8主要采用Scavenge算法。在Scavenge算法的具体实现中,主要采用了Cheney算法。Cheney算法是一种采用复制的方式实现垃圾回收的算法。它将新生代堆内存一分为二,每一部分空间称为semispace。当开始进行垃圾回收时,会检查From空间中的存活对象,这些存活对象将被复制到To空间中,而非存活对象占用的空间将会被释放。完成复制后,From空间和To空间的角色发生对换,称为翻转。
对于老生代的垃圾回收,V8主要采用标记清除算法。标记清除算法主要通过判断某个对象是否可以被访问到,从而知道该对象是否应该被回收。具体步骤如下:垃圾回收器会在内部构建一个根列表,用于从根节点出发去寻找那些可以被访问到的变量。