一 目录
不折腾的前端,和咸鱼有什么区别
目录 |
---|
一 目录 |
二 前言 |
三 栈垃圾回收 |
四 堆垃圾回收 |
五 新生代和老生代 |
5.1 新生代 - 副垃圾回收器 |
5.2 老生代 - 主垃圾回收器 |
六 全停顿 |
七 参考文献 |
二 前言
返回目录
原始数据类型是存储在栈空间中的,引用类型的数据是存储在堆空间中的。
不过有些数据被使用之后,可能就不再需要了,我们把这种数据称为垃圾数据。
如果这些垃圾数据一直保存在内存中,那么内存会越用越多。
所以我们需要对这些垃圾数据进行回收,以释放有限的内存空间。
这时候就引入了垃圾回收机制。
垃圾回收策略分为 手动回收 和 自动回收 两种:
- 手动回收:C/C++ 语言可以通过
free()
这些代码来决定何时分配内存、何时销毁内存。 - 自动回收:JavaScript、Python、Java 等产生的垃圾数据是由垃圾回收器来释放的。
三 栈垃圾回收
返回目录
当函数执行结束,JavaScript 引擎通过向下移动 ESP
指针(记录调用栈当前执行状态的指针),来销毁该函数保存在栈中的执行上下文(变量环境、词法环境、this
、outer
)。
如果有代码:
function foo(){
var a = 1;
var b = { name: 'jsliang' };
function showName(){
var c = 2;
var d = { name: 'zhazhaliang' };
}
showName();
}
foo();
在上面代码中,根据浏览器策略:
- 先到全局执行上下文
- 再到
foo
函数执行上下文 - 最后到
showName
函数执行上下文
根据栈的特性,此时 JavaScript 情况应为:
调用栈 |
---|
showName 函数执行上下文 |
foo 函数执行上下文 |
全局执行上下文 |
然后开始释放过程:
- 执行到
showName
函数的时候,创建showName
函数的执行上下文,并将showName
函数的执行上下文压入栈中。 - 以此同时,我们会有一个记录当前执行状态的指针(称为
ESP
),指向调用栈中showName
函数的执行上下文,表示当前正在执行showName
函数。 - 当
showName
函数执行完毕,函数执行流程进入foo
函数,这时候 ESP 就下移到foo
函数的执行上下文。这个下移操作就是销毁showName
函数执行上下文的过程。
如果 showName
函数下面还调用了另一个函数,那么它会覆盖 showName
函数的内容,用来存放另一个函数的执行上下文。
就是说:当一个函数执行结束之后,JavaScript 引擎会通过向下移动 ESP
来销毁保存栈中的执行上下文。
四 堆垃圾回收
返回目录
在上面,我们将栈中的垃圾通过 ESP
进行回收了:
function foo(){
var a = 1;
var b = { name: 'jsliang' };
function showName(){
var c = 2;
var d = { name: 'zhazhaliang' };
}
showName();
}
foo();
但是,里面的 b
和 d
其实是依旧存在于堆中的。
要回收对象的垃圾数据,就需要 JavaScript 中的垃圾回收器了。
五 新生代和老生代
返回目录
代际假说特点:
- 大部分对象存活时间很短
- 不被销毁的对象,会活的更久
这两个特点不仅仅适用于 JavaScript,同样适用于大多数的动态语言,如 Java、Python 等。
V8 中会把堆分为 新生代 和 老生代 两个区域,新生代中存放的是生存时间短的对象,老生代中存放的生存时间久的对象。
新生区的容量没有老生区那么大,所以 V8 分别使用 2 个不同的垃圾回收器,来高效实施垃圾回收:
- 副垃圾回收器。主要负责新生代的垃圾回收。
- 主垃圾回收器。主要负责老生代的垃圾回收。
5.1 新生代 - 副垃圾回收器
返回目录
算法:Scavenge 算法
对象区域 | 空闲区域 |
---|---|
... | ... |
原理:
- 把新生代空间对半划分为两个区域,一半是对象区域,一半是空闲区域。
- 新加入的对象都会存放到对象区域,当对象区域快被写满时,就需要执行一次垃圾清理操作。
- 先对对象区域中的垃圾做标记,标记完成之后,把这些存活的对象复制到空闲区域中
- 完成复制后,对象区域与空闲区域进行角色翻转,也就是原来的对象区域变成空闲区域,原来的空闲区域变成了对象区域。
对象晋升策略:经过两次垃圾回收依然还存活的对象,会被移动到老生区中。
5.2 老生代 - 主垃圾回收器
返回目录
- 算法:标记 - 清除(Mark-Sweep)算法
- 原理:
- 标记:标记阶段就是从一组根元素开始,递归遍历这组根元素,在这个遍历过程中,能到达的元素称为活动对象,没有到达的元素就可以判断为垃圾数据。
- 清除:将垃圾数据进行清除。
对一块内存多次执行标记 - 清除算法后,会产生大量不连续的内存碎片。而碎片过多会导致大对象无法分配到足够的连续内存。
这时候就需要标记整理。
- 算法:标记 - 整理(Mark-Compact)算法
- 原理:
- 标记:和标记 - 清除的标记过程一样,从一组根元素开始,递归遍历这组根元素,在这个遍历过程中,能到达的元素标记为活动对象。
- 整理:让所有存活的对象都向内存的一端移动
- 清除:清理掉端边界以外的内存
六 全停顿
返回目录
由于 JavaScript 是单线程的,所以一旦执行垃圾回收算法,那正在执行的 JavaScript 脚本需要暂停下来,等垃圾回收完毕之后再恢复脚本执行。
这种行为叫 全停顿(Stop-The-World)。
如果堆中的数据较多,那么回收需要时间,会造成页面卡顿状态,所以为了降低这个卡顿,V8 将标记过程分为一个一个子标记过程,同时垃圾回收标记和 JavaScript 应用逻辑交替进行。
- 优化算法:增量标记(Incremental Marking)算法
- 原理:
- 为了降低老生代的垃圾回收而造成的卡顿
- V8 把一个完整的垃圾回收任务拆分为很多小的任务
- 让垃圾回收标记和 JavaScript 应用逻辑交替进行
七 参考文献
返回目录
- JavaScript进阶-内存机制(表情包初探)【阅读建议:20min】
jsliang 的文档库由 梁峻荣 采用 知识共享 署名-非商业性使用-相同方式共享 4.0 国际 许可协议 进行许可。<br/>基于 https://github.com/LiangJunrong/document-library 上的作品创作。<br/>本许可协议授权之外的使用权限可以从 https://creativecommons.org/licenses/by-nc-sa/2.5/cn/ 处获得。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。