js垃圾回收机制
垃圾回收器
- js引擎中的一个后台进程----监听所有对象,删除不可访问对象
- 在 JavaScript 内存管理中有一个概念叫做 可达性,就是那些以某种方式可访问或者说可用的值,它们被保证存储在内存中,反之不可访问则需回收
/* 案例一 */
let user = {
name: "John"
};
/* 全局环境 可以通过访问user就可以得到{name:'John'} */
user = null
/* 这样{name:'John'}就不能访问了,就要被回收 */
/* 案例二 */
// user具有对象的引用
let user = {
name: "John"
};
let admin = user;
/* 全局环境 可以通过访问user或者admin就可以得到{name:'John'} */
user = null
/* 这样{name:'John'}就不能访问了,就要被回收 */
/* 案例三 */
function marry (man, woman) {
woman.husban = man;
man.wife = woman;
return {
father: man,
mother: woman
}
}
let family = marry({
name: "John"
}, {
name: "Ann"
})
/* 如图neicun1 */
delete family.father;
delete family.mother.husband;
/* 如图neicun2 neicun3 neicun4*/
/* John 现在是不可访问的,并将从内存中删除所有不可访问的数据。 */
什么是垃圾?
没有被引用的对象,有个例外如果几个对象引用形成一个环,互相引用,但根访问不到它们,这几个对象也是垃圾,也要被清除。
为什么要垃圾回收?
声明变量时,都会分配内存存储,当不再使用时,就要及时释放,否则内存满了造成系统崩溃
标记清除
- 当变量进入环境时,就标记这个变量为进入环境
- 当变量离开环境时,就标记为离开环境。
- 垃圾回收器在运行的时候会给存储在内存中的变量都加上标记(所有都加),然后去掉环境变量中的变量,以及被环境变量中的变量所引用的变量(条件性去除标记),删除所有被标记的变量,删除的变量无法在环境变量中被访问所以会被删除,最后垃圾回收器,完成了内存的清除工作,并回收他们所占用的内存。
- 在清除之后,剩余的对象内存位置是不变的,也会导致空闲内存空间是不连续的,出现了 内存碎片,并且由于剩余空闲内存不是一整块,它是由不同大小内存组成的内存列表,这就牵扯出了内存分配的问题
- 内存碎片化,空闲内存块是不连续的,容易出现很多空闲内存块,还可能会出现分配所需内存过大的对象时找不到合适的块
- 分配速度慢,最坏情况是每次都要遍历到最后,同时因为碎片化,大对象的分配效率会更慢
var a="a";
var b="b";
a = b;
引用计数
另一种不太常见的方法就是引用计数法,引用计数法的意思就是每个值没引用的次数,当声明了一个变量,并用一个引用类型的值赋值给改变量,则这个值的引用次数为 1,;相反的,如果包含了对这个值引用的变量又取得了另外一个值,则原先的引用值引用次数就减 1,当这个值的引用次数为 0 的时候,说明没有办法再访问这个值了,因此就把所占的内存给回收进来,这样垃圾收集器再次运行的时候,就会释放引用次数为 0 的这些值。
let a = new Object() // 此对象的引用计数为 1(a引用)
let b = a // 此对象的引用计数是 2(a,b引用)
a = null // 此对象的引用计数为 1(b引用)
b = null // 此对象的引用计数为 0(无引用)
用引用计数法会存在内存泄露,下面来看原因:
function test(){
let A = new Object()
let B = new Object()
A.b = B
B.a = A
}
对象 A 和 B 通过各自的属性相互引用着,按照上文的引用计数策略,它们的引用数量都是 2,但是,在函数 test 执行完成之后,对象 A 和 B 是要被清理的,但使用引用计数则不会被清理,因为它们的引用数量不会变成 0,假如此函数在程序中被多次调用,那么就会造成大量的内存不会被释放
缺点
引用计数的缺点想必大家也都很明朗了,首先它需要一个计数器,而此计数器需要占很大的位置,因为我们也不知道被引用数量的上限,还有就是无法解决循环引用无法回收的问题,这也是最严重的
JavaScript 对象生命周期的理解?
当创建一个对象时,JavaScript 会自动为该对象分配适当的内存垃圾回收器定期扫描对象,并计算引用了该对象的其他对象的数量如果被引用数量为 0,或惟一引用是循环的,那么该对象的内存即可回收