在JavaScript中,垃圾回收是一种自动内存管理机制,它可以自动地识别不再使用的变量和对象,并将它们从内存中清除,以释放内存空间。在项目运行与用户使用的过程中,当可以被垃圾回收机制识别清除的变量达到一定阈值时,就会触发垃圾回收机制。虽然各浏览器对该阈值大小的算法不同,但毫无疑问的是,如果短时间内频繁出现不再使用的变量,则会频繁触发垃圾回收机制,从而占用内存并影响项目性能,对于浏览器内存普遍偏小的移动端设备,这种影响有时是不可忽略的,因此在项目中合理控制变量的使用,是提升项目性能的一个重要方面,本文将介绍一种利用对象池避免频繁触发垃圾回收机制的思路,以达到提升项目性能的目的。
THE
/
01
JS的垃圾回收机制简介
JavaScript中的垃圾回收机制主要有两种:标记清除法和引用计数法:
1、标记清除法
标记清除法的工作原理是:垃圾回收器会定期扫描内存中的对象,从根对象开始遍历内存中的所有对象,对于可达对象,通过标记它们来标识它们是可达对象;对于未被标记的对象,就说明它们是不可达对象,需要被清除。该算法的优点是可以处理循环引用的情况,但在执行时间上可能会比较长,影响程序的性能。其主要步骤如下:
①创建一个根对象。
②遍历根对象及其所有引用的对象,并标记它们是可达对象。
③遍历内存中所有对象,如果发现某个对象未被标记,就将其清除。
2、引用计数法
引用计数法的工作原理是:垃圾回收器会记录每个对象被引用的次数,当对象被引用的次数为0时,就将该对象清除。该算法的优点是实现较为简单,但无法处理循环引用的情况,可能会导致内存泄漏。其主要步骤如下:
①给每个对象添加一个引用计数器,初始值为0。
②当对象被引用时,引用计数器加1。
③当对象不再被引用时,引用计数器减1。
④当引用计数器为0时,就将该对象清除。
THE
/
02
利用对象池提升项目性能的思路
为了提升 JavaScript 性能,最后要考虑的一点往往就是压榨浏览器了。此时,一个关键问题就是如何减少浏览器执行垃圾回收的次数。开发者无法直接控制什么时候开始收集垃圾,但可以间接控制触发垃圾回收的条件。理论上,如果能够合理使用分配的内存,同时避免多余的垃圾回收,那就可以保住因释放内存而损失的性能。
浏览器决定何时运行垃圾回收程序的一个标准就是对象更替的速度。如果有很多对象被初始化,然后一下子又都超出了作用域,那么浏览器就会采用更激进的方式调度垃圾回收程序运行,这样当然会影响性能。例如:
function addVector(a, b) {
let resultant = new Vector();
resultant.x = a.x + b.x;
resultant.y = a.y + b.y;
return resultant;
}
调用这个函数时,会在堆上创建一个新对象,然后修改它,最后再把它返回给调用者。如果这个对象的生命周期很短,那么它会很快失去所有对它的引用,成为可以被回收的值。假如这个函数频繁被调用,那么垃圾回收调度程序会发现这里对象更替的速度很快,从而会更频繁地安排垃圾回收。
该问题的解决方案是不要动态创建对象,比如可以修改上面的函数,让它使用一个已有的对象:
function addVector(a, b, resultant) {
resultant.x = a.x + b.x;
resultant.y = a.y + b.y;
return resultant;
}
当然,这需要在其他地方实例化对象参数 resultant,但这个函数的行为没有变。那么在哪里创建对象可以不让垃圾回收调度程序盯上呢?
一个策略是使用对象池。在初始化的某一时刻,可以创建一个对象池,用来管理一组可回收的对象。应用程序可以向这个对象池请求一个对象、设置其属性、使用它,然后在操作完成后再把它还给对象池。由于没发生对象初始化,垃圾回收探测就不会发现有对象更替,因此垃圾回收程序就不会那么频繁地运行。下面是一个对象池的伪实现:
// vectorPool 是已有的对象池
let v1 = vectorPool.allocate();
let v2 = vectorPool.allocate();
let v3 = vectorPool.allocate();
v1.x = 10;
v1.y = 5;
v2.x = -3;
v2.y = -6;
addVector(v1, v2, v3);
console.log([v3.x, v3.y]); // [7, -1]
vectorPool.free(v1);
vectorPool.free(v2);
vectorPool.free(v3);
// 如果对象有属性引用了其他对象
// 则这里也需要把这些属性设置为null
v1 = null;
v2 = null;
v3 = null;
如果对象池只按需分配矢量(在对象不存在时创建新的,在对象存在时则复用存在的),那么这个实现本质上是一种贪婪算法,有单调增长但为静态的内存。这个对象池必须使用某种结构维护所有对象,例如数组。
THE
/
03
使用对象池的优缺点
对象池基于创建时屏蔽获取细节、对象在当前生命周期终结时入池的基本理念。可以有效降低因变量频繁离开上下文从而触发垃圾回收机制进而导致的性能损失,其本质上是一种拿内存换性能的算法思想,因此具有明显的优点和缺点。
优点:
①动态修改对象属性比创建对象的内存开销小。
②复用池中对象,消除创建对象、回收对象所产生的内存开销、cpu开销以及产生的网络开销。
缺点:
①由于池中对象的数量有限,势必成为一个扩展性瓶颈。
②在池中对象数量有限的同时很难正确的设定对象池的大小,如果太小则起不到作用,如果过大,则占用内存资源高。
文章作者:潘嘉伟
封面设计:Lina
原文始发于微信公众号(EBCloud):浅谈JS中如何利用对象池提升项目性能
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论