Mercer-Lee的空间

vuePress-theme-reco Mercer-Lee的空间    2018 - 2024
Mercer-Lee的空间 Mercer-Lee的空间

Choose mode

  • dark
  • auto
  • light
TimeLine
分类
  • 数据结构和算法
  • 后端
  • 运维
  • 前端
  • 工具
  • 语言
标签
我的GitHub (opens new window)
author-avatar

Mercer-Lee的空间

27

文章

29

标签

TimeLine
分类
  • 数据结构和算法
  • 后端
  • 运维
  • 前端
  • 工具
  • 语言
标签
我的GitHub (opens new window)
  • 简单了解 V8 的垃圾回收算法

    • V8 的垃圾回收
      • 分代式垃圾回收机制
        • 新生代内存处理
          • Scavenge 算法
        • 老生代内存处理
          • Mark-Sweep 算法
          • Mark-Compact 算法
          • 结合使用
        • 其余的优化
          • 结语

          简单了解 V8 的垃圾回收算法

          vuePress-theme-reco Mercer-Lee的空间    2018 - 2024

          简单了解 V8 的垃圾回收算法


          Mercer-Lee的空间 2022-02-21 Node JS 算法

          # V8 的垃圾回收

          在我们的日常开发中,很少会遇到内存泄漏的情况,哪怕遇到了也可以通过使用 Buffer 或者放开 V8 的内存限制来解决(非常不推荐)。但是作为一个 NodeJS 开发者,我觉得很有必要认识 Node 的 V8 引擎是垃圾回收大概是怎么样的,它用到了什么样的算法去实现,也可以看下他跟 Java 的 JVM 或者其他语言的引擎有啥不同。

          # 分代式垃圾回收机制

          分代式垃圾回收机制是非常常见的一种垃圾回收机制,因为长时间的发展中,大家发现没有一种算法可以应付所有的情况,于是按对象的存活时间将内存的垃圾回收进行不同的处理,分别对不同的对象实施不同的更高效的算法。比如分代式指的就是把内存分为老生代和新生代。

          v8

          V8的内存的大小一般情况下64位的系统是1.4 GB,32位的系统是0.7 GB。而老生代内存的空间分别是64位系统是1400 MB,32位系统下为700 MB,新生代内存是比较小的,64位系统下32 MB,32位系统下是16 MB。既然区分了新生代和老生代,也知道了内存大小,我们接下来就来处理这两个区间的内存对象。

          # 新生代内存处理

          # Scavenge 算法

          在新生代的内存中 V8 主要通过 Scavenge 算法来进行垃圾回收。这个算法是一个采用一分为二的方式来实现的,它将新生代内存空间分为两份,每份空间叫 semispace,一个空间使用(From),一个空间闲置(To)。当我们分配对象时,先是在 From 空间中进行分配。当垃圾回收开始的时候,会检查 From 空间中的存活对象,这些存活对象会被复制到 To 空间中,然后 From 空间非存活对象占用的空间被释放,最后 From 空间和 To 空间的角色发生对换。

          很明显,Scavenge 是典型的用空间换时间的算法,因为需要空闲出一半的内存空间来完成算法,虽然速度很快,但是牺牲了一半的空间,所以只适合在新生代内存空间这种生命周期很短,空间比较小的处理上。

          scavenge

          到了这里,很多人就疑惑,如果一个对象经过多次回收一直存活,岂不是在做无用功?确实,如果一个对象长期存活,在频繁回收的新生代空间里面是一个浪费,这时候我们就需要把这个对象处理下了:我们把经历过一次 Scavenge 回收的对象复制到老生代内存空间:

          scavenge2

          但是还有一个问题 To 空间的大小问题,一次垃圾回收不是只回收一个对象,From 空间转移到 To 空间的过程中有可能会有比较多的内存对象被复制到 To 空间中去,这时候如果 To 空间不够用或者太小了就很比较麻烦,所以我们还得增加一个限制:

          scavenge3

          # 老生代内存处理

          在老生代内存空间中,老生代的对象都是存活对象占的比重比较大,而且空间也比较大,用 Scavenge 算法已经捉襟见肘了,所以需要用不一样的算法。

          # Mark-Sweep 算法

          Mark-Sweep 算法分为两个阶段:标记和清除。首先标记阶段,Mark-Sweep 在标记阶段遍历堆中的所有对象,标记活着的对象,然后清理死亡对象即可,由于老生代中死亡对象相对比较小,所以效率很快。

          mark-sweep

          但是清除回收之后有一个问题,内存空间出现了不连续的状态,这种问题会导致后续需要分配一个大的对象的时候没有办法完成此次分配,然后提前触发垃圾回收。为了解决这个问题,Mark-Compact 算法来了。

          # Mark-Compact 算法

          Mark-Compact 算法跟 Mark-Sweep 算法不同的是它是直接标记死亡对象,然后把活着的对象往一端移动,最后把另外一端的内存全部清除掉。

          mark-compact

          # 结合使用

          这两种算法各有优劣,很明显,Mark-Compact 算法虽然可以解决内存空间碎片的问题,但是效率肯定是没有 Mark-Sweep 算法来的高的,因为他需要移动内存对象,所以在 V8 中主要是使用 Mark-Sweep 算法,直到内存空间不足以对对象分配时才会使用 Mark-Compact 算法。

          # 其余的优化

          上面的算法和策略解决了基本的垃圾回收,但是 V8 的垃圾回收没有那么的简单:执行垃圾回收的时候通常需要暂停 JavaScript 应用逻辑,因为不暂停的话会出现 JavaScript 应用和垃圾回收器所获得的内存对象结果不一致的情况。在新生代的垃圾回收中暂停可能不会有什么影响,毕竟新生代内存小,存活对象也少,但是在老生代内存空间中,空间大,存活对象多,标记、清理和整理的过程会比较长,这时候就会让暂停的时间变得很长,这是无法接受的,所以 V8 引擎将常规的标记改为了增量标记,就是拆成很多步来实现完成完整的内存标记,这样可以让暂停的时间减少。同理,V8 还引入了延迟清理、增量式整理这些优化方法。。。

          # 结语

          其实 V8 引擎的垃圾回收远远不止那么简单,但是我们这次先大概了解下基本的内存处理的算法。