GC统治前端的垃圾回收江湖
2023-10-25 19:09:01
八股文不用背,浏览器的GC也能发光发热
谈起GC,恐怕前端同学第一反应就是八股文,"垃圾回收机制(GC)是计算机系统回收不再使用的内存空间的一种技术"。诚然,垃圾回收对前端开发并不算新鲜,JS诞生至今已经20多年,浏览器早已演变成一个极其复杂、庞大、无比精细的工程了。
作为浏览器的核心之一,GC的作用举足轻重,正所谓牵一发而动全身,GC的任何举措都会在很大程度上影响到整个浏览器的表现,甚至决定前端开发的体验。
大家知道,JS代码是运行在V8引擎中的,全局JS和函数在运行时分别会创建全局上下文和函数上下文,并都会压进一个执行栈中,非引用类的数据就放在上下文中,引用类的则放在堆内存中,那么浏览器是怎么样对上下文中这些所谓的垃圾数据进行清理的呢?
大概就是这么个过程:标记 -> 标记结束 -> 标记根节点是否死亡 -> 标记根节点是否活着 -> 根节点死亡则进行清理 -> 根节点活着则不进行清理。整个过程是通过垃圾回收算法来完成的,主流的垃圾回收算法有:
-
标记-清除算法:简单粗暴、可理解性强,缺点就是没有空间局部性,并且标记清除过程十分耗时;
-
引用计数算法:算法简单、可理解性强,内存回收过程快捷,并且由于JS垃圾回收是个多线程的过程,引用计数在回收垃圾时不会引起阻塞,但是在现实场景中,像循环引用这种奇葩玩意儿就能够让算法失效;
-
标记-整理算法:解决了标记-清除算法中空间局部性问题,同样也会存在回收过程耗时的问题;
-
分代收集算法:把内存分为年轻代和老年代,新创建的对象会先进入年轻代,并且会经过垃圾回收而被释放,当对象一直存活并且不断被引用时会进入老年代,这个算法的特点在于根据对象的年龄对内存进行分类,在年轻代时进行快速回收,老年代的对象因为经过多次的年轻代回收的存活了下来,很可能已经不需要了,所以老年代回收的频率较低,这种针对内存回收效率和性能的分类有效避免了频繁的垃圾回收。
大家有兴趣的话可以查阅Chrome浏览器中V8引擎对这些算法的应用,各家浏览器厂商会有自己的实现,具体算法和细节会有差异。
另外,相信很多同学都知道,GC的存在会影响JS代码的执行效率,那么我们该如何避免GC带来的性能问题?
其实说白了就是尽量减少不必要的垃圾产生,来避免GC的执行频率,以下这些方面值得关注:
- 减少变量声明
function generateObject() {
// ...
for (let i = 0; i < 100000; i++) {
const obj = {
id: i,
name: 'A' + i
}
arr.push(obj)
}
// ...
}
- 使用数字代替字符串
for (let i = 0; i < 100000; i++) {
const name = 'A' + i
arr.push(name)
}
for (let i = 0; i < 100000; i++) {
arr.push(i)
}
- 别给全局变量赋值
for (let i = 0; i < 100000; i++) {
globalVar = i
}
总之,理解GC的原理,进而针对其特点进行优化,是提高前端应用性能的一大途径。