1.可达的内存
v8 本身有内存回收机制,并且会回收 所有不可达的内存,那么什么是 可达的内存呢?
从全局执行上下文出发,能找到的,就被认为是可达的
如下所示, obj 就是 可达的,而 obj1 obj2 就是不可达的
var obj = {parent: obj}
function fn() {
const obj1 = {}
const obj2 = {}
obj1.name = obj2
obj2.name = obj1
}
而如下所示,user 就是可达的,name 属性是不可达的
const user = {age: 1}
const list = [user.age] // 引用 + 1
function fn() {
const name = 'jack'
}
fn()
2. v8 中的内存机制
64位 1.5G 32位 800M
新生代 | 老生代 | |
内存大小(64位) | 32M | 14G |
内存大小(32位) | 16M | 700M |
执行的操作 | 复制、标记整理 | 标记清除、标记整理、增量标记 |
- 其中新生代 还分为 两个区域,一个是from,一个是to,专门用来执行 复制 操作
- 所谓的复制 操作 指的是 在 from 区域 中的内存要 用完之前,就会把 当前 可达的内存 复制一份,到 to 区域中去,这其中还 可以顺便整理内存空间,使得获得一个 比较大的连续的内存空间
- 然后 在一轮的 GC 之中还存活着的话,那就把 新生代的 内存放到 老生代去
- 在这之后,to 空间 就变成了 from 空间,而原来的 from 空间就会被清空,成为 to 空间,也就是进行 调换
下面重点来简单介绍一下 各种内存 处理的算法
执行过程 | 是否可以立即执行 | 缺点 | 优点 | |
引用计数 | 引用 计数器,判断当前的引用数是否为0,当为0的话,就可以直接删除 | 是,只要计数器为 0,可以立即执行 | 1、无法回收循环引用的对象 2、 空间开销大 | |
标记清除 | 1、遍历所有对象找标记活动对象 其实就是从代码层面 去 找一遍能找到的对象, global 开始查找 找到了就进行标记 2、遍历 所有对象清除没有标记对象 然后 把所有没有被标记的 给 删除 | 否 | 1、容易导致 空间的碎片化,空间的不连续 2、不能立即回收对象,回收的时候,程序停止工作 | 解决了 循环引用的问题 |
标记整理 | 当想把 新生代 往老生代的空间移动,但是不足以 完成晋升,就会对老生代执行 和 标记清除一样,但是会对空间进行整理,让空间能够出现连续 | 否 | 不能立即回收对象,回收的时候,程序停止工作,最长也就1s | 这里的执行比较缓慢,毕竟是对 老生代这么大的东西进行整理,所以会导致 js 的性能问题 |
标记增量 | 1、对需要进行清除的 内存进行 一点一点地进行标记,但是并不立刻删除 2、然后 标记一点,就执行一段 js 代码,接着再 标记一点,又去执行一点代码(类比于 js 中的 requestIdeCallback) 3、等到标记完成了,对所有标记的 垃圾对象进行一次性删除 | 否 | 执行的时候会导致 js 停止执行 | 并不会导致 js 一次性停止太长的时间,提高用户体验 |