可达性
可达性是指内存中的值通过某种方式进行访问,该值被长期放置在内存中。
如:
var obj = {
name: '张三',
age: 12,
other: {
height: 180
}
}
如上所展示,其中张三, 12, 180甚至他们所在的对象都是可达的,因为他们都被obj及其子孙变量(如:name, age, other, height)所引用,其他变量可以通过obj及其子孙变量访问到它们。我们称之为可达对象(又称为活跃对象)。
如果此时我们将obj重新赋值为null,那么obj与{ name: '张三', age: 12, other: { height: 180 } }的关联就会断开,此时其他变量无法通过obj访问到,那么此时他们就称之为不可达对象(又称为非活跃对象),即使他们相互引用(180被height变量引用,height变量被other变量引用)。
可达性的更多知识可以参考:garbage collection
固有可达值
- 全局的局部变量和参数
- 当前嵌套调用链上的其他函数的变量和参数
- 全局变量
- 还有一些其他的,内部的
以上情形产生的值我们称为固有可达值,又被称之为根。
第一点和第三点都好理解,因为浏览器的全局变量可以简单理解为window,该变量在任何地方都是可以调用的,我们认为window可达,所以其内部的变量和函数也是可达的。
如果我们在控制台中创建一个函数,那么这个函数会自动挂载在window变量上,所以我们调用sum()和sum()是一样的道理。
对于第二点的理解:
function sum (a, b) {
return function (c) {
return a + b + c;
}
}
var a = sum(1, 2);
console.log(a(3)); // 输出6
因为sum是挂载在window上的,所以其参数及变量都是可达的,变量c通过a + b + c的方式进行了引用,所以c也被认为是可达的。
以上理解为个人理解,如有偏差,敬请指正,为感。
堆和栈
内存中含有堆内存和栈内存,在JS中,引用类型(如:object,array,new Date等)的值都被放入堆内存中,而非引用类型(如:string,number等)的值都被放入栈内存中。
想要更加详细了解堆和栈,可以参考:js堆栈的理解
内存针对堆内存和栈内存的特点采用不同的回收方式及策略。
栈回收
栈内存回收相对而言比较简单,主要是:JS引擎通过向下移动ESP指针(栈指针)来销毁存放在栈空间中的执行上下文。记住这里是执行上下文,或者称之为(执行上下文环境)。
var a = 1;
function foo() {
var b = 2;
var c = 1001;
};
// 函数调用
foo();
栈就像是往一个桶中放石头,最新丢进去的石头我们称之为全局上下文环境,它是不会自动清理的,除非用户自己变比了页面。当一个函数被调用时,就会产生一个执行上下文环境,这个环境执行完毕后会自动清理,除非产生了闭包,也就是固有可达值中的第二点。
栈中的执行上下文是通过ESP指针来进行回收的。
回收(GC)过程:
1、当页面第一次进入时,产生全局上下文环境并进入栈中,ESP指针指向该环境;
2、当调用foo函数时,产生foo执行上下文环境并入栈,此时ESP指向foo执行上下文环境;
3、当foo函数执行完毕,ESP向下调整,指向全局上下文环境;
4、当有其他函数执行时,该函数会入栈,覆盖foo执行上下文环境所在的空间,并将ESP指针指向其;
5、执行完毕后重新向下调整,指向全局上下文环境;
6、页面关闭时,全局上下文环境销毁。
具体流程如下图所示:
堆回收
堆回收的过程和栈回收的过程不同,不是通过ESP指针进行清理,而是经过了标记-清理-内存整理的过程。
因为绝大多数对象的生存周期非常短,小部分对象的生存周期非常长,所以V8针对该特点将堆空间分为了新生代空间和老生代空间。
新生代空间体积较小,通常为1-8M,不是完全固定,根据具体情况进行分配。
老生代空间体积较大,通常存放经常调用或体积较大的值。
针对两者特点,采用的回收方式也是不同的,但是大过程还是标记-清理-内存整理的过程。
新生代回收
新生代内存将其空间划分为:Eden区域和Survivor区域,Survivor区域又分为From Space和To Space。
回收(GC)过程:
1、当一个对象产生时,会进入新生代(大体积的对象会直接进入老生代)的Eden区域;
2、当一个上下执行环境执行完毕后,如果该对象仍然被引用,那么标记其为活跃对象,否则为非活跃对象;
3、当Eden区域区域内存耗尽时,将活跃对象按照进入的顺序将其移动至From Space,Eden区域剩下的就全部都是非活跃对象;
4、将Eden区域中的对象全部清空;
5、将Survivor区域和Eden区域整体翻转;
6、新对象不断加入,当Eden区域内存耗尽,则重复2,3,4,5步,其中,如果第一次存活的对象继续存活,则将其放入To Space中;
7、经过两次CG仍然存活的对象我们认为其是生存周期长的对象,将其从To Space放入老生代空间。
上述策略我们称之为对象晋升策略,算法叫做Scavenge 算法,新生代算法可以说没有内存整理阶段,因为对象进入Survivor区域时是有序的,所以不存在断层(即碎片)。
具体图示过程如下图:
老生代回收
老生代回收机制比新生代回收机制要简单,用的是标记-清除法。即当一个上下执行环境执行完毕后,如果该对象仍然被引用,那么标记其为活跃对象,否则为非活跃对象,当内存耗尽时,将非活跃对象进行清除,并对所有活跃对象重新排序,整理碎片。
参考文献
以上内容并不是说浏览器垃圾回收机制只采用了上述算法和策略,其实还有其他,只是说主要以上述过程为主。
以上内容为个人根据相关参考文献进行整理而成,如有偏差,敬请指正,为感。
1、garbage collection
2、V8之旅:垃圾回收器
更多相关文档,请见:
线上地址 【前端橘子君】
GitHub仓库【前端橘子君】