内存泄漏
js的垃圾回收机制是为了防止内存泄漏而设计的,内存泄漏的含义就是某块内存不再被需要或无法被引用或引用链从根访问时,这块内存却还存在着。
垃圾回收机制
垃圾回收机制就是间歇性的不定期去寻找不再使用的变量,并释放掉它们所指向的内存。
JavaScript中内存管理的主要概念就是可达性。
简单地说,可达性 就是那种以某种方式可访问或可被引用的值,它们被保证存储在内存中。
当然,如果引用或引用链可以从根访问到任何其他值,则会认为该值是可访问的。例如,如果局部变量中的 A 对象具有引用到 B 对象的属性,则 A,B 属性皆被视为具有可达性。
但是有一组对象持有固有的可达值,所以就无法被回收删除,例如
- 全局变量
- 本地函数的局部变量和参数
- 当前嵌套调用链上的其他函数的变量和参数
- 还有其他的,内部的
这些值统称为 根
所以在js中不被引用的都是垃圾。但是如果几个对象互相引用,但是根无法访问,那么它们也是垃圾!
下面是一些内存管理的例子
最简单的例子
// user has a reference to the object
let user = {
name: "John"
};
这里将一个键name值John的对象赋值给user
如果user的值被覆盖,也就是引用丢失
user = null;
现在John就会变成不可达的状态,无法访问无法引用,垃圾回收器将丢弃John对象数据并释放内存
如果有两个引用指向同一个内存地址
// user has a reference to the object
let user = {
name: "John"
};
let admin = user;
其中一个变量被覆盖
user = null;
John对象仍然可以从admin全局变量中访问,如果admin变量也被覆盖,John对象才会被释放。
相互关联的对象
function marry(man, woman) {
woman.husband = man;
man.wife = woman;
return {
father: man,
mother: woman
}
}
let family = marry({
name: "John"
}, {
name: "Ann"
});
使用marry函数结合两个对象,相互引用
到目前为止,所有对象都是可访问的。
现在,让我们移除两个引用:
delete family.father;
delete family.mother.husband;
现在的情况是根family可以引用通过mother变量引用到Ann,John可以通过wife变量引用到Ann,但是John无法被其他变量引用。
垃圾回收之后
无法访问的数据块
这种情况还是会存在的,例如给family数据块变量值给覆盖
family = null;
内存中状态就会变成
很显然,family 对象已经从根上断开了链接,不会再有引用,那么他整个块都将变得不可达,并将会被删除。
内部算法
其实很简单,就是 ‘标记-删除’
例如结构如下
第一步 垃圾回收机制给根标记
第二步 访问并标记所有来自他们的引用
以及子孙后代的引用
第三步 删除没有被标记的对象
打个比方,一家公司里面有职员A,虽然职员A有公司的联系方式,但是公司上下却联系不上职员A,那么职员A就会被回收掉。
当然,JavaScript会运用很多优化,使得项目运行的更快,例如 分代回收,增量回收,空闲时间回收。
额外
另一种不太常见的垃圾收集策略叫做引用计数。引用计数的含义是跟踪记录每个值被引用的次数。
顾名思义,当一个对象被引用时,引用1次+1,如果该对象的其中一个引用对象被覆盖,则引用次数-1,以此类推,当引用次数为0时,下次垃圾回收器执行时,引用次数为0的对象会被回收掉。
但是有一个问题,当两个对象相互引用时,那么两个对象的引用次数都是2,这两个对象的引用次数就永远不会为0,就会造成内存无法回收的现象。为此放弃了引用计数的回收方式转而采用标记清除的垃圾回收机制。