垃圾回收

本文最后更新于:2020年11月5日 凌晨

垃圾回收

JS是自动内存管理实现内存分配和闲置资源回收;

确定哪个变量不会再使用,然后释放它占用的内存;过程是周期性质的,即垃圾回收程序每隔一定时间(或者说在代码执行过程中某个预定的收集时间)就会自动运行。

垃圾回收两种主要的标记策略:

  • 标记清理;
  • 引用计数;

标记清理

JavaScript 最常用的垃圾回收策略是标记清理(mark-and-sweep)。

  • 当变量进入上下文,比如在函数内部声明一个变量时,这个变量会被加上存在于上下文中的标记。
  • 而在上下文中的变量,逻辑上讲,永远不应该释放它们的内存,因为只要上下文中的代码在运行,就有可能用到它们。
  • 当变量离开上下文时,也会被加上离开上下文的标记。

垃圾回收程序运行的时候,会标记内存中存储的所有变量(记住,标记方法有很多种)。

然后,它会将所有在上下文中的变量,以及被在上下文中的变量引用的变量的标记去掉。

在此之后再被加上标记的变量就是待删除的了,原因是任何在上下文中的变量都访问不到它们了。

随后垃圾回收程序做一次内存清理,销毁带标记的所有值并收回它们的内存。

引用计数

其思路是对每个值都记录它被引用的次数。

声明变量并给它赋一个引用值时,这个值的引用数为 1。如果同一个值又被赋给另一个变量,那么引用数加 1。

类似地,如果保存对该值引用的变量被其他值给覆盖了,那么引用数减 1。

当一个值的引用数为 0 时,就说明没办法再访问到这个值了,因此可以安全地收回其内存了。

但是引用计数可能存在循环引用:

1
2
3
4
5
6
7
// 当离开该函数的作用域,objectB和objectA计数都是2,但是用于无法归0,内存就永远无法还原
function problem() {
let objectA = new Object(); // 1
let objectB = new Object(); // 1
objectA.someOtherObject = objectB; // 2
objectB.anotherObject = objectA; // 2
}
1
2
3
4
5
6
7
8
9
// 还有一种相互引用也也会造成永远不会清除
let element = document.getElementById("some_element");
let myObject = new Object();
myObject.element = element;
element.someObject = myObject;

// 如果需要清除那么设置为null
myObject.element = null;
element.someObject = null;

性能与内存管理

我们需要注意: 无论什么时候开始收集垃圾,都能让它尽快结束工作。

1
2
// IE中该方法会立即触发垃圾回收
window.CollectGarbage()

将内存占用量保持在一个较小的值可以让页面性能更好:

  • 解除引用: 如果数据不再必要,那么把它设置为 null,从而释放其引用。这个建议最适合全局变量和全局对象的属性。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    function createPerson(name){ 
    let localPerson = new Object();
    localPerson.name = name;
    return localPerson;
    }
    let globalPerson = createPerson("Nicholas");
    // 解除 globalPerson 对值的引用
    globalPerson = null;
    // 解除对一个值的引用并不会自动导致相关内存被回收。解除引用的关键在于确保相关的值已经不在上下文里了,因此它在下次垃圾回收时会被回收。
  • 通过 constlet 声明提升性能:

    使用这两个新关键字可能会更早地让垃圾回收程序介入,尽早回收应该回收的内存。

  • 隐藏类和删除操作

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    function Article() { 
    this.title = 'Inauguration Ceremony Features Kazoo Band';
    }
    let a1 = new Article();
    let a2 = new Article();

    // 此时重新声明一个属性,就导致了对应了不同的隐藏类
    a2.author = 'Jake';


    // 解决该问题最好的方式就是在构造函数中一次性声明好所有的属性,避免动态属性
    function Article(opt_author) {
    this.title = 'Inauguration Ceremony Features Kazoo Band';
    this.author = opt_author;
    }
    // 此时a1,a2共享相同的隐藏类,
    let a1 = new Article();
    let a2 = new Article();
    // 当某一个实例不需要一个属性的时候最好设置成null,使用delete关键字可能再一次改变隐藏类
    delete a1.author;
    a1.author = null;
  • 内存泄漏

    具体请看闭包和内存泄漏


垃圾回收
http://www.clearluv.com/2020/11/05/垃圾回收/
发布于
2020年11月5日
许可协议