V8引擎和JS垃圾回收机制的详解

1.V8引擎和垃圾处理机制

1.1、v8

1.1.1、V8的内存限制

一般后端语言中,基本的内存使用上没有什么限制**,然后node中通过js使用内存会发现,只能使用部分内存,64位系统约1.4GB, 32位系统约0.7GB。**如果我们读入内存一个2GB的文件,即使物理内存有32GB,但在这样单个Node进程的情况下,计算机的内存资源无法得到充足使用。

造成这个问题的原因是什么?

主要因为Node基于V8构建,所以Node中使用js对象基本上都是V8自己的方式进行分配和管理,V8的内存管理机制在浏览器场景下使用绰绰有余,但在Node中限制了开发者随心所欲使用大内存的想法。

1.1.2、V8的对象分配

V8中,对象都是通过堆来分配的,我们可以通过process.memoryUsage()来查看内存的使用情况。该函数返回值中heapTpal是申请到的堆内存,heapUsed是当前使用量。当我们声明变量并赋值的时候,所使用对象就在堆内存中。如果堆的空闲空间不足以分配给新对象,会继续申请堆内存,直到堆的大小超过V8的限制。

为何限制堆内存的大小?

  1. 因为对于网页来说V8的限制值已经绰绰有余
  2. 根本原因是垃圾回收机制的限制。(官方说法1.5GB的垃圾回收堆内存,V8需要50ms以上,做一次非增量式的垃圾回收甚至需要1s以上)。而垃圾回收会引起JS线程暂停执行,在这样时间花销下,应用的性能和响应能力都会下降,这样后端和前端都无法接受

如何内存的限制?

以下两个最大值需要在启动时就指定

  1. 使用命令node --max-old-space-size=1700 test.js //单位为MB 老生代的内存空间
  2. node --max-new-space-size=1700 test.js//单位KB 新生代的内存空间

1.2、垃圾回收机制

1.2.1、内存分代

人们发现没有任何垃圾回收算法能够胜任所有场景(对象生存周期的长短不一),不同的算法只能针对特定的情况具有最好的效果。因此现代的垃圾回收算法中按照对象的存活时间将内存的垃圾回收进行不同的分代(新生代和老生代),然后分别对不同分代的内存施以更高效的算法,新生代的对象为存活时间较短的对象,老生代的对象为存活时间较长或常驻内存的对象。V8堆的整体大小就是新生代+老生代的内存空间

1.2.1.1、新生代内存

新生代内存由两个reserved_semispace_size所构成,按机器位数在64为上reserved_semispace_size的值为16MB,32位的是8MB。所以新生代内存空间最大值在64位系统和32位系统上分别位32MB和16MB

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v9dS8FAN-1660181620949)(笔记依赖图/image-20220810184031455.png)]

从该图片我们可以得知,V8最大保留空间是4*reserved_semispace_size_+max_old_gendration_size_

1.2.2、scavenge算法-针对于新生代

在Scavenge具体实现中,主要采用了Cheney算法,Cheney算法采用一种复制方式实现垃圾回收算法,

Cheney算法原理

  1. 划分空间,将堆内存一分为二,每一部分空间成为semispace,在这两个semispace空间中,只有一个处于使用中,另一个处于闲置状态,使用状态的semispace空间成为From空间,处于限制状态的空间成为To空间。
  2. 赋值,当我们分配对象时,先在From空间进行分配。当开始进行垃圾回收时,会检查From空间中存活的对象,将存活的对象复制到To空间中,而非存活对象占用的空间将被释放。
  3. 完成复制后,From空间和To空间角色发对换。简而言之,就是垃圾回收过程中,通过将存活对象在两个semispace空间之间进行复制。

Scavenge算法的缺点

  1. 只能使用内存的一半,典型的空间换时间,但由于scavenge由于只赋值存活对象,并且对于生命周期短的场景存活对象只占少部分,因此时间效率比较高
  2. 空间换时间的这种算法,无法大规模应用到所有垃圾回收中,因此scavene只适合在新生代中。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BlzUQEdV-1660181620950)(笔记依赖图/image-20220811084318568.png)]

晋升机制

当一个对象经过多次复制依然存活时,它将会被认为是生命周期较长的对象。这种对象随后会被移动到老生代总,采用新的算法进行管理。

晋升条件

  1. 是否经历过Scavenge回收
    • 对象从From空间到To空间时,就会检查它的内存地址来判断该对象是否经历过一次Scavenge回收,如果已经经历过了,会将该对象从From空间复制到老生代空间中,如果没有就复制到To空间中。
  2. To空间内存占用比超过限制
    • 当从From空间到To空间时,如果To空间使用已经超过了25%,则该对象直接晋升到老生代空间中。
    • 设置25%限制原因:因为Scavenge回收完成后,这个To空间会变成From空间,接下来内存会在该空间进行分配,如果占比过高,会影响后续内存分配

1.2.3、Mark-Sweep & Mark-Compact-针对于老生代

老生代不采用Scavenge算法原因

  1. 老生代中存活对象占比大,因此复制存活对象效率低。
  2. 浪费一半空间

Mark-Sweep标记清除

Mark-Sweep在标记阶段遍历堆中所有对象,并标记存活对象,在随后清除阶段中,只清除没有被标记的对象。**因为在老生代中死对象占据较小一部分,而Mark-Sweep只清除死亡对象,就像Scavenge算法,新生代中存活对象占小部分,其算法只处理存活对象,**这样效率就有了提升。

Mark-Sweep算法的缺点

该算法最大问题在于进行一次标记清除回收后**,内存空间会出现不连续的情况**。这种内存碎片会对后续的内存分配造成问题。因为很可能出现需要分配一个大对象的情况,而碎片空间无法完成分配,就会提前触发垃圾回收,而这次回收是不必要的。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KfKN3ngS-1660181620950)(笔记依赖图/image-20220811090125259.png)]

Mark-Compact弥补Mark-Sweep碎片空间缺点

Mark-Compact是标记整理,该算法将存活对象往一端进行移动,移动完成后,直接清理掉最右边存活对象后面的内存区域完成回收

image-20220811090250581

缺点:速度慢-需要整理对象

优点:空间开销少

1.2.4、Incremental Marking-针对垃圾回收引起全停顿

为了避免JS应用逻辑和垃圾回收期看到的不一致,垃圾回收的3中基本算法都需要将逻辑暂停下来,等垃圾回收完成后再继续执行,这种行为被称为全停顿在V8分代式垃圾回收中,一次小垃圾回收只收集新生代,由于新生代默认配置小,存活对象少,因此即使是全停顿也影响不大,而对于老生代通常配置大,存活对象多,全堆垃圾回收的标记、清理、整理等动作造成的停顿会比较可怕。

V8为了降低全堆垃圾回收带来的停顿时间,先从标记阶段入手将原本一口气停顿完成的动作改为增量标记,拆分许多小“步进”,每做完一“步进”就让JS应用逻辑执行一小会儿,垃圾回收和JS逻辑交替执行,直到标记阶段完成,V8经过标记改进后,垃圾回收的最大停顿时间减少到了原本的1/6.

V8后续还引进了延迟清理Lazy-Sweeping和增量式整理incremental Compaction,让清理与整理动作也变成增量式,同时还计划引入并行标记和并行清理,进一步利用多核性能降低每次停顿时间。

圾回收的最大停顿时间减少到了原本的1/6.

V8后续还引进了延迟清理Lazy-Sweeping和增量式整理incremental Compaction,让清理与整理动作也变成增量式,同时还计划引入并行标记和并行清理,进一步利用多核性能降低每次停顿时间。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大鲤余

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值