V8引擎 您所在的位置:网站首页 GC垃圾收集器的作用是什么 V8引擎

V8引擎

2023-04-23 02:47| 来源: 网络整理| 查看: 265

【译】JavaScript 工作原理:V8 编译器的优化 Deoptimization in V8 一、什么是 V8 JS & WebAssembly 引擎; 使用 C++ 编写; 可独立运行,也可被任何 C++ 应用调用; 发展 2008 年,发布并开源; 2010 年,支持 JIT; 2011 年,Isolate; 2015 年,支持 Code Cache、Snapshot、Ignition; 后面更多,支持了 ES6、ES8、WebAssembly、FastProperties、GC Optimization; 二、V8 是如何解析执行 JS 的

image

1. AST(Abstract Sytax Tree)

image

经过 Tokenize + Parse => AST

image

使用 d8 –print-ast test.js 可以看到生成的抽象语法树的数据结构:

image

2.Scope

使用 d8 –print-scopes test.js 可以看到分析得到的作用域:

image

从 生成语法树和作用域分析 可以看到,只解析了顶层代码(foo 函数内部实现并未解析),函数只在被调用时才会被解析。

3.Bytecode

AST + Scope => Bytecode

使用 d8 –print-bytecode test.js 可以看到生成的字节码。

image

4. Ignition Interpreter

1)AST + Scope => Bytecode 2)Execute Bytecode

image

即时解释这种有着更快的启动速度和更少的内存,但字节码执行起来较慢。

5. JIT Optimization

为了解决即时解释执行较慢的问题,发明了 JIT(just-in-time compilation) 技术(即时编译)。

利用 TurboFan Compiler ,可以将 ByteCode => Machine Code(二进制码)。

JIT 会找到 HotSpot(热点,即反复执行多次的函数),然后将其编译成二进制,下次会直接执行这些二进制。

image

6.De-Optimization (去优化)

Machine Code => ByteCode

因为 js 是一个动态语言,在运行时对象的结构有可能会被改变,而机器码(二进制码)是只支持固定结构。所以,当一个 HotSpot 冷却后,机器码会被重新还原为字节码。

三、 V8 是如何进行 GC 的

image

Node 进程启动时, V8 会申请一个内存空间,同时 Node 申请一块内存空间,用于 Buffer、Connection 等存储。GC 是指对 V8 堆内存和 V8 堆外内存 的垃圾进行回收和碎片整理的过程。

V8 使用一个叫 Orinoco(奥里诺科)的 Garbage Collector 进行 gc。

Orinoco is a high-performance, low-latency, mostly parallel and concurrent garbage collector.

1. 标记 garbage

image

对栈中没有引用的非全局 object ,标记为垃圾。

2. 回收过程

2.1 Minor GC - Scavenger

把堆划分为 Young Generation 和 Old Generation, 新生代区维护两个区域,分别是 From-Space 和 To-Space, 用于 gc 时存活 Object 的复制。

image

1)经历完第一次 gc 而没被回收的 Nursery Object,会被复制到另一个 Space, 被称为 Intermediate Object;

image

2)Intermediate Object 如果在第二次 gc 中也没被回收,就会被复制到 Old Generation 区。

image

Minor GC - Scavenger 耗时较少,较轻量。

2.2 Major GC

动态语言中,像 js、php、python 等,大多数 Object 都不会存活太久,“Most object die young”。

经历了两次 gc 的 Object 被复制到老生代区,时间长了,老生代里对象越来越多,如果不加清除,会导致内存爆炸。

清理老生代区的过程称为 Major GC,分为 mark-sweep(标记-清扫)会 mark-compact(标记-使紧凑)。

2.2.1 mark - sweep

mark-sweep 就是把从根节点无法获取到的对象清理掉,与 scavenge 相比,scavenge 只会复制存活对象,而新生代区域中 Object 本身就小,且存活对象不多,所以高效。mark-sweep 则只会清除没被标记的对象,而老生代死对象少,这也就是 mark-sweep 针对老生代区域高效 GC 的原因。

2.2.1 mark - compact

由于经过 mark-sweep 算法 GC 后,会出现不连续的空间,导致空间碎片,当下次需要移动大对象或对象晋升,但没有足够的空间使用。

mark-compact 是对老生代区内存碎片的整理,会将内存碎片标记至 freelist,然后对这些空间进行回收,释放老生代区的可分配空间。

image

image

3. 三个特征

首先是一个多线程 parallel的。gc 依赖一个线程池,里面有一些 helper 子线程帮助主线程进行 gc。

image

另外,是一个增量回收 incremental的过程, gc 会不断间歇的标记回收一些 chunk,所以不会阻塞主线程。

image

最后,gc 是伴随 js 在主线程的执行,同时发生 concurrent的。主线程忙碌时,gc 子线程可以自发去进行 gc,不依赖主线程。

image

4. gc 部分参考资料

张秋怡在 node 大会上的分享

Orinoco: The new V8 Garbage Collector Peter Marshall

V8 新生代垃圾回收的实现

V8 堆外内存 ArrayBuffer 垃圾回收的实现

node-v8 堆内存分析

四、 V8 在 Node.js 的作用和基础概念

V8 在 Node.js 里面主要是有两个作用,第一个是负责解析和执行 JS。第二个是支持拓展 JS 能力,作为这个 JS 和 C++ 的桥梁。

4.1 基础概念

Isolate:

代表一个 V8 的实例,它相当于这一个容器。通常一个线程里面会有一个这样的实例。比如说在 Node.js 主线程里面,它就会有一个 Isolate 实例。

Context:

代表我们执行代码的一个上下文,它主要是保存像 Object,Function 这些我们平时经常会用到的内置的类型。如果我们想拓展 JS 功能,就可以通过这个对象实现。

ObjectTemplate:

用于定义对象的模板,然后我们就可以基于这个模板去创建对象。

FunctionTemplate:

和 ObjectTemplate 是类似的,它主要是用于定义一个函数的模板,然后就可以基于这个函数模板去创建一个函数。

FunctionCallbackInfo:

用于实现 JS 和 C++ 通信的对象。

Handle:

管理在 V8 堆里面那些对象,因为像我们平时定义的对象和数组,它是存在 V8 堆内存里面的。Handle 就是用于管理这些对象。

HandleScope:

是一个 Handle 容器,HandleScope 里面可以定义很多 Handle,它主要是利用自己的生命周期管理多个 Handle。

下面我们通过一个代码来看一下 HandleScope 和 Handle 它们之间的关系。

image

首先第一步新建一个 HandleScope,就会在一个栈里面定义一个 HandleScope 对象。然后第二步新建了一个 Handle 并且把它指向一个堆对象。这时候就会在栈里面分配一个叫 Local 对象,然后在堆里面分配一块 slot 所代表的内存和一个 Object 对象,并且建立关联关系。当执行完这个函数的时候,这个栈就会被清空,相应的这个 slot 代表的内存也会被释放,但是 Object 所代表这个对象,它是不会立马被释放的,它会等待 GC 的回收。

五、实战: V8 的使用 5.1 通过 V8 执行 JS

image

首先第一步新建一个 Isolate,它这表示一个隔离的实例。第二步定义一个 HandleScope 对象,因为我们下面需要定义 Handle。第三步定义一个 Context,这是代码执行所在的上下文。第四步定义一些需要被执行的 JS 代码。第五步通过 Script 对象的 Compile 函数编译 JS 代码。编译完之后,我们会得到一个 Script 对象,然后执行这个对象的 Run 函数就可以完成代码的执行。

5.2 拓展 JS 的原有能力

接下来再看一下怎么去拓展 JS 原有的一些能力。

image

首先第一步是通过 Context 上下文对象拿到一个全局的对象,类似于在前端里面的 window 对象。第二步通过 ObjectTemplate 新建一个对象的模板,然后接着会给这个对象模板设置一个 test 属性, 值是函数。接着通过这个对象模板新建一个对象,并且把这个对象设置到一个全局变量里面去。这样我们就可以在 JS 层去访问这个全局对象。

5.3 通过 V8 实现 JS 和 C++ 层通信

下面我们通过使用刚才 5.2 中定义那个全局对象来看一下 JS 和 C++ 是怎么通信的。

image

当在 JS 层调用刚才定义 test 函数时,就会相应的执行 C++ 层的 test 函数。这个函数有一个入参是 FunctionCallbackInfo,在 C++ 中可以通过这个对象拿到 JS 传来一些参数,这样就完成了 JS 层到 C++ 层通信。经过一系列处理之后,还是可以通过这个对象给 JS 层设置需要返回给 JS 的内容,这样可以完成了 C++ 层到 JS 层的通信。

Previous Node.js 中的事件循环 Next 盘点我的2022


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有