内存控制
Mark-Sweep & Mark-Compact
对于老生代的对象,存活对象占较大的比重,采用scvenge方法会存在如下问题
1. 存活对象较多,复制存活对象的效率很低
2. 浪费一半空间的问题
因此v8在老生代中主要采用了Mark-Sweep和Mark-Compact相结合的方式进行垃圾回收
Mark-Sweep
不将内存空间划分为两半,不存在浪费一半空间的行为。Mark-Sweep在标记阶段遍历堆中的所有对象,标记活着的对象,在随后的清除阶段,只清除没有标记的对象。对比Scanvenge.Scavenge中只复制活着的对象,而Mark-Sweep只清理死亡的对象。活对象在新生代中只占较小的部分,死对象在老生代中只占较小的部分。
Mark-Sweep
最大的问题是在进行一次标记清楚回收之后,内存空间会出现不连续的状态。这种内存碎片会对后续的内存分配造成问题。
Mark-Compact
是标记整理,在Mark-Sweep之后演变回来的。差别在于对象在标记为死亡之后,在整理的过程中,将获得对象往一侧移动,移动完成hi后,直接清理掉边界外的内存。
全暂停停顿和增量标记
为了避免出现JavaScript应用程序和垃圾回收器不一致的情况,垃圾回收的三种基本算法都需要将应用程序暂停下来,等待执行完垃圾回收之后,在恢复应用逻辑,这种行为被称为全暂停停顿
。
为了降低全堆垃圾回收带来的停顿时间,v8在2011年引入了增量标记
的算法。增量标记将垃圾回收的执行过程分为多个小阶段,在每个小阶段中,垃圾回收器只标记一部分存活的对象,然后将标记的结果进行合并,最后一次性将存活的对象复制到新的内存空间中。
javascript还引入了延迟清理
和增量式整理
以上三种算法的对比
在v8的垃圾回收过程中,会输出一些日志信息,可以通过`--trace-gc`来开启垃圾回收日志的输出。
$node --trace-gc
高效使用内存
在v8面前,开发者需要让垃圾回收机制更高效的工作
作用域
在JavaScript中,能形成作用域的有函数调用,with以及全局作用域
var foo = function() {val local = {}
}
在函数调用的时候会形成响应的作用域,函数执行完成之后,作用域会销毁。函数作用域中声明的局部变量分配在该作用域上,随着作用域的销毁而销毁。在这个实例中,对象非常小,会被分配在新生代中的from空间中,在作用域释放之后,局部变量失效,引用的对象会在下次垃圾回收的时候被释放
标识符查找
var bar = function () {console.log(local)
}
javascript在执行时候回去查找变量定义。先查找当前作用域,如果在当前作用域中没有办法找到该变量的声明,就会像上级作用域中去找。直到找到为止。
作用域链
var foo = function() {var local = 'local var'var bar = function() {var local = 'another var'var baz =function () { console.log(local)}baz()}
bar()
}
foo()
变量的主动释放
如果变量是全局变量,由于全局作用域需要直到进程退出才能释放,此时导致引用的对象常驻内存,如果需要释放常驻内存的对象,可以通过delete操作来删除引用关系,将变量重新赋值,将旧的对象脱离关系。
gloval.foo = "I am global object"
console.log(global.foo)
delete global.foo //
global.foo = undefined
console.log(global.foo) // undefined
闭包
闭包是JavaScript的高级特性,利用他可以产生很多巧妙的效果,问题在于,一旦有变量引用则个中间函数,这个中间函数不会发生释放,同时也会使得原始的作用域不会得到释放。作用域中产生的内存占用也不会得到释放
小结
在JavaScript执行中,无法立即回收的内存有闭包和全局变量引用情况。