【每日一题NO.61】JavaScript 里垃圾回收机制是什么?
共 2237字,需浏览 5分钟
·
2021-10-16 18:32
JavaScript 中的垃圾回收
V8(JS 引擎)的新老空间内存分配与大小限制
新老空间
凡事都有一把双刃剑,在垃圾回收的演变过程中人们发现,没有一种特定的垃圾回收机制是可以完美的解决问题,因此 V8 采用了新生代与老生代结合的垃圾回收方式,将内存分为 <新生代> 和 <老生代>。新生代频繁进行 GC,空间小,采用的是空间换时间的 scavenge
算法,所以又划分两块 semispace,From 和 To。老生代大部分保存的是存活时间较长的或者较大的对象。采用的是 mark-sweeep
(主)& mark-compact
(辅)算法。
V8 限制了 JS 对象可以使用的内存空间,不止是因为最初 V8 是作为浏览器引擎而设计的,还有其垃圾回收机制的影响因素。V8 使用 stop-the-world
(全停顿) generational,accurate 的垃圾回收器。在执行回收之时会暂停中断程序的执行,而且只处理对象堆栈。当内存达到一定的体积时,进行一次垃圾回收的时间将会很长,从而影响其相应而造成浏览器假死的状况。因此,在 V8 中限制老生代 64 位为 1.4GB,32 位为 0.7GB,新生代 64 位为 32M,32 位为 16M。当然,如果需要更大的内存空间,在 node 中可以进行更改。
对象晋升
新生成的对象放入新生代内存中,那么哪些对象会被放入老生代呢?大部分放入老生代的对象是由新生代晋升而来。对象的晋升方式:
当新生代的 To semispace
内存占满 25%时,此时在从 From semispace
拷贝对象将不会再放入 To 空间中以防影响后续的新对象分配,而将其直接复制到老生代空间中。
在进行一次垃圾回收后,第二次 GC 时,发现已经经历过一次 GC 的对象在从 From 空间复制时直接复制到老生代
在新对象分配时大部分对象被分配到新生代的 From semispace
,但当这个对象的体积过大,超过 1MB 内存页时,直接分配到老生代中的 large Object Space。
新生代的 GC 机制与优缺点
回收机制
新生代采用 Scavenge
算法,在 Scavenge 算法的实现过程中,则主要采用 cheney
算法。即使用复制方式来实现垃圾回收,它将内存一分为二,每一个空间都是一个 semispace
。
处于使用状态的是 From 空间,闲置的是 To 空间。当分配对象时,先是分配到 From 空间,垃圾回收会检查 From 空间中存活的对象,将其复制到 To 空间,回收其他对象。完成复制后会进行紧缩,From 和 To 空间的调换,如此循环往复。
优势
由其执行的算法及过程我们了解到,在新生代的垃圾回收过程中,总是有一半的 semispace 是空余的。Scavenge 只复制存活的对象,在新生代的内存中,存活的对象相对较少,所以使用这个算法恰到好处。
老生代的 GC 机制与优缺点
回收机制
由于 scavenge 算法只能复制存活的对象,如果在老生代中也使用此算法的话会造成复制很多对象,效率低,并且造成很大的内存空间浪费。老生代中采用的则是 mark-sweep
(标记清除)和 mark-compact
(标记整理)结合的方式。
而为什么使用两者结合呢?这就要讲到两者的优点和缺点。
mark-sweep(标记清除)
优点:
标记清除需要标记堆内存中的所有在使用的对象,清除那些没有被标记的对象。在老生代内存中与新生代相反,不使用的对象只占很小一部分,所以清除不用的对象效率较高
mark-sweep
不会将内存空间分为两半,所以,不会浪费一半空间。
缺点:
但标记清除会造成一个问题,就是在清除过后会导致内存不连续,造成内存碎片,如果此时需要存储一个很大的内存而空间又不够的时候会造成没有必要的反复垃圾回收。
mark-compact(标记整理)
优点:
此时标记整理就可以出场了,在标记清除的过程中,标记整理会将存活的对象和需要清除的对象移动到两端。然后将其中一段需要清除的消灭掉,可以解决标记清除造成的内存碎片问题。
缺点:
但是在紧缩内存的过程中需要移动对象,效率比较低。所以 V8 在清理时主要会使用 mark-sweep
,在空间不足以对新生代中晋升过来的对象进行分配时才会使用 mark-compact
。
垃圾回收机制的优化
增量标记(在老空间里引入了此方式)
scavenge 算法 mark-sweep 及 mark-compact 都会导致 stop-the-world (全停顿)。而全停顿很容易带来明显的程序迟滞,标记阶段很容易就会超过 100ms,因此 V8 引入了增量标记,将标记阶段分为若干小步骤,每个步骤控制在 5ms 内,每运行一段时间标记动作, 就让 JavaScript 程序执行一会儿, 如此交替,明显地提高了程序流畅性,在一定程度上避免了长时间卡顿。
所有《每日一题》的 知识大纲索引脑图 整理在此:https://www.yuque.com/dfe_evernote/interview/everyday
你也可以点击文末的 “阅读原文” 快速跳转