垃圾回收
本文最后更新于:2020年11月5日 凌晨
垃圾回收
JS是自动内存管理实现内存分配和闲置资源回收;
确定哪个变量不会再使用,然后释放它占用的内存;过程是周期性质的,即垃圾回收程序每隔一定时间(或者说在代码执行过程中某个预定的收集时间)就会自动运行。
垃圾回收两种主要的标记策略:
- 标记清理;
- 引用计数;
标记清理
JavaScript 最常用的垃圾回收策略是标记清理(mark-and-sweep)。
- 当变量进入上下文,比如在函数内部声明一个变量时,这个变量会被加上存在于上下文中的标记。
- 而在上下文中的变量,逻辑上讲,永远不应该释放它们的内存,因为只要上下文中的代码在运行,就有可能用到它们。
- 当变量离开上下文时,也会被加上离开上下文的标记。
垃圾回收程序运行的时候,会标记内存中存储的所有变量(记住,标记方法有很多种)。
然后,它会将所有在上下文中的变量,以及被在上下文中的变量引用的变量的标记去掉。
在此之后再被加上标记的变量就是待删除的了,原因是任何在上下文中的变量都访问不到它们了。
随后垃圾回收程序做一次内存清理,销毁带标记的所有值并收回它们的内存。
引用计数
其思路是对每个值都记录它被引用的次数。
声明变量并给它赋一个引用值时,这个值的引用数为 1。如果同一个值又被赋给另一个变量,那么引用数加 1。
类似地,如果保存对该值引用的变量被其他值给覆盖了,那么引用数减 1。
当一个值的引用数为 0 时,就说明没办法再访问到这个值了,因此可以安全地收回其内存了。
但是引用计数可能存在循环引用:
1 |
|
1 |
|
性能与内存管理
我们需要注意: 无论什么时候开始收集垃圾,都能让它尽快结束工作。
1 |
|
将内存占用量保持在一个较小的值可以让页面性能更好:
解除引用: 如果数据不再必要,那么把它设置为 null,从而释放其引用。这个建议最适合全局变量和全局对象的属性。
1
2
3
4
5
6
7
8
9function createPerson(name){
let localPerson = new Object();
localPerson.name = name;
return localPerson;
}
let globalPerson = createPerson("Nicholas");
// 解除 globalPerson 对值的引用
globalPerson = null;
// 解除对一个值的引用并不会自动导致相关内存被回收。解除引用的关键在于确保相关的值已经不在上下文里了,因此它在下次垃圾回收时会被回收。通过 const 和 let 声明提升性能:
使用这两个新关键字可能会更早地让垃圾回收程序介入,尽早回收应该回收的内存。
隐藏类和删除操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21function 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;内存泄漏
具体请看闭包和内存泄漏