JavaScript
具有自动垃圾回收机制,对于开发人员来说是无感知的,通常都是浏览器在代码运行中自动进行的。
为什么会有垃圾回收?
举个例子,人有生老病死,但是人如果不会死亡,那么人口就会不断增长,最终将会超过地球的负载,地球也将崩溃。
我们开发的程序也是一样,程序运行中如果只在内存中增加变量,不去销毁内存中一些已经无用的变量,那么程序运行到某个时间将会程序内存将会达到一个峰值,程序也将随之崩溃。
可达性
JavaScript
中内存管理的主要概念是可达性。
可达性值就是指能够以某种方式可访问的值或可用的变量值,他们将会一直保存在内存中,只有程序退出后才会销毁他们。
显而易见,js会有一组常见的可达值:
1、全局变量
2、函数作用域中的变量及参数
3、当前嵌套调用链上的其他函数的变量和参数
这些值称为根
任何可以从根访问到变量值都可以认为是“可达值”
举个例子:
var a = {} // 此时该对象是可放达的
a = null // 将null赋值给了a,那么最开始赋值给a的变量就再也访问不到了,那他就会被回收
理解垃圾回收
目前常用的垃圾回收机制包括:标记清除与引用计数
标记清除
变量在进入环境时,将这个变量标记为“进入环境”,当环境离开时标记为“离开环境”。垃圾收集器在运行时会给存储在内存中的所有变量加上标记,他会去掉环境中的变量以及被环境中变量引用的变量的标记
永远不能释放进入环境的变量所占用的内存,因为你很难保证在后面不会用到它。
采取标记的方法有很多,你可以选择自己所喜欢的,比如通过翻转字符串等。
举个例子:
let a = 0;
let b = 0;
function A(c){
A = function(d) {
console.log(c+d)
}
console.log(c++)
}
A(1)
A(2)
标记清除上面的代码过程如下:
- 程序开始执行,垃圾收集器标记所有变量:a,b,A,参数c,参数d。
- A(1)执行,程序执行栈进入函数A,垃圾收集器将进行标记清除:清除环境变量参数c和内部函数A;函数执行结果为1
- A(2)执行,程序进入内部函数A,垃圾收集器进行标记清除,清除环境中参数变量d,由于在步骤2中,外部A函数的环境变量参数c的标记被清除,c的内存将不会释放,c在经过步骤2后更新为2,所以此时结果为4;
标记清除是目前运用最广泛的垃圾回收算法。
引用计数
通过跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型值赋给该变量时,则这个值的引用次数就是1,如果同一个值又被赋值给另一个变量,则引用次数加1。相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数减一,当这个值得引用次数变成0时,则说明没有办法在访问这个值了,因而就可以将其占用的内存空间回收回来。
let a = {name:"CSDN"} //{name:"CSDN"}引用次数为1
let b = a; // 将a赋值给b,b实际上还是引用的{name:"CSDN"},因此他的引用次数加1变成2;
a = null; // a的引用变成了null,a无法在访问这个对象,该对象的引用次数就降为1;
b = 0; // 同理,b取得了其他值,不再引用该对象,该对象的引用次数此时就降为了0,在此之后该对象就可以被回收了
如果只是上面例子中这种简单的使用看起来似乎没有什么问题,但在实际使用过程中,引用计数将会因为循环引用导致内存始终无法释放。看依稀啊下面的例子:
function problem(){
let objectA = new Object(); // 该对象记为A,A对象的引用次数此时为1
let objectB = new Object(); // 该对象记为B,B对象的引用次数此时为1
objectA.b = objectB; // objectA引用了B对象,B的医用次数就变为2;
objectB.a = objectA; // objectB引用了A对象,A的医用次数就变为2;
}
可以看到在本例中,当函数执行完毕,两个对象的引用次数都大于0,因此无法释放内存,这就存在了内存泄漏问题。
但如果使用标记清除,在函数开始执行后将会清除objectA与objectB的标记,后面也没有为其添加标记的方法,因此函数执行结束,变量就会被清除。