javascript 内存管理
javascript中 有垃圾回收机制,。 垃圾回收机制会中断整个代码的执行;释放不可能再被使用的变量,释放内存,这个工作是周期性的;
可释放对象
function fn1() {
var obj1 = { name: 'xiaomuchen', age: '20' }
}
function fn2() {
var obj2 = { name: 'xiaomuchen', age: '20' }
return obj2
}
var a = fn1()
var b = fn2()
console.log(a, b) // undefined, {name: "xiaomuchen", age: "20"}
对比上面2 个函数,发现 fn1 是没有 return 值的; 表示在此函数执行后,这个变量便不再访问了; fn2 函数在最后 return obj2 ,返回了变量;所以依然可以访问;
总结: javascript 回收机制通过判断变量是否可以被访问,来决定回收哪些变量;
那么 JavaScript 是如何判断变量是否可被访问了? 引入了标记清除和引用计数;
标记清除 引用计数
-
引用清除
标记清除是目前 大部分JavaScript 引擎使用的判断方式,通过标记变量的状态来确定是否可以被回收,当变量在环境中被声明时标记 进入环境,在环境中的任何位置,任何时间都可以被访问;当环境被销毁,则变量被标记离开环境,等待被回收;
function fn(){
var a = { count: 10 } // 被标记,进入环境
var b = { count: 20 } // 被标记,进入环境
}
fn(); // 执行完毕之后 b 被标记,离开环境
-
引用计数:
JavaScript 引擎维护一张
引用表,保存内存中所有的资源的引用次数。资源被引用一次则引用 +1,资源被去掉引用或者退出变量的函数作用域时,则引用 -1,当资源的引用次数为0时,说明无法访问这个值,则等待回收。
function fn(){
var a = { count: 10 } // 资源 { count: 10 } 被引用次数为 1
a = { count: 20 } // 资源 { count: 20 } 被引用次数为 1,资源 { count: 10 } 被引用次数为 0,等待回收
// do someThing
}
fn(); // 资源 { count: 20 } 被释放
但是引用计数存在一种循环引用的情况,如下例子,两个对象之间相互引用,在离开环境后对象不可访问,但由于对象的引用次数为 1,则导致不会被回收。这个例子来自《JavaScript 高级程序设计》,但我思考良久,如果引用计数把 a.param 也作为一个变量来计数,那么就没有这个问题了,引用计数实现的方式不同,产生的结果也不一样。
致命的缺点:无法回收循环引用的对象
function fn(){
var a = { count: 10 }
var b = { count: 20 }
a.param = b // b 的引用次数为 2
b.param = a // a 的引用次数为 2
}
fn(); // a、b 的引用次数为 1
GC 缺点 以及解决方法
GC 会中断代码的执行,因为要遍历所有的对象,回收所有不可访问的对象,这个操作耗时可能在 100ms 以上; 耗时很长;
解决方法:
- 分代回收
目的是通过使用的频率,存在时长,区分新生代 和老生代对象; 多回收新生代对象,少回收老生代区,减少每次遍历的对象,减少 GC 的消耗,
- 增量 GC
把需要长耗时的遍历、回收操作,拆分进行,减少中断时间,但是会增大上下文切换开销;