可达性
可达性是指内存中的值通过某种方式进行访问,该值被长期放置在内存中。
如:
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仓库【前端橘子君】