程序是怎样跑起来的?V8 浅析
我正在参加「掘金·启航计划」
前言
我们知道程序是由“指令+数据”的组成,运行程序的是CPU,只要电子产品存在的地方,都有CPU的痕迹。CPU类似人体的大脑,处理着高强度且复杂的计算,运用在我们互联网世界的千千万万,图形绘制、数据存储、路由基站等。
那 CPU 中包含什么?CPU 是寄存器的集合体(程序计数器、标志寄存器、累加寄存器……)
- 寄存器------暂存指令、数据等处理对象-----相当于内存的一种
- 控制器------负责把内存上的指令、数据等读入到寄存器上,并根据指令的执行结果控制整个计算机
- 运算器------负责运算从内存读入到寄存器的数据
- 时钟--------负责发出CPU开始计时的时钟信号
整体流程:
程序启动后,根据时钟信号从内存中读取指令和数据,通过控制器对指令加以解释和运行,运算器就会对数据进行运算,控制器根据运算结果控制计算机。
那么提个问题,Windows上的应用,能在MacOS上运行吗?
不能,运行环境 = 操作系统 + 硬件,不同的系统的 Api 不同,应用程序向操作系统传递指令的途径也是不同的
API: 应用程序向操作系统发送指令的途径
一、JAVA 运行机制
简介
不管是 Kotlin、Java 等语言,只要在 Java 平台上运行,最终都需转化为 class 字节码,再交由 JVM 进行解释执行。
当然 JVM 也会分出很多版本,如上述 CPU 接收的指令不同,有对应 mac、windows以及Android端的Dalvik 虚拟机。
编译阶段:
Java源代码通过 Java 编译器编译成字节码文件(.class文件),在编译的过程中,编译器会进行语法检查、类型检查、生成字节码等操作。
解释阶段:
Java虚拟机(JVM)将字节码文件解释成机器码并执行。
在解释的过程中,JVM会进行类加载、字节码校验、准备、解析、初始化等一系列操作,最终生成可执行代码并执行。
需要注意的是,Java运行机制中的解释并不是传统意义上的解释,而是指将字节码文件解释成机器码并执行的过程。这种嵌套的解释结构可以带来更好的跨平台性和安全性。
二、V8 浅析
同 JVM 一样,是提供给 Java 运行的平台,V8 则是提供给 JavaScript 运行的虚拟机。
V8 是 Google 的开源高性能 JavaScript 和 WebAssembly 引擎,用 C++ 编写。它用于 Chrome和 Node.js 等。
1. 执行概述
简单的说,就是将源代码通过 Parse 编译成 AST,然后 Ignition 解析成字节码,最后 Turbofan 将字节码编译成 Machine-code(汇编)
2. V8 主要模块
V8 大量代码都是由 C++ 编写,这里只讲述上述流程中主要模块
Parse:
类似 Javac 对源代码进行编译,转换成 AST(抽象语法树),可以思考为什么 babel能将es6->es5,ts->js,底层原理也都是通过 AST
esprima.org/demo/parse.… 可以去这个网站进行验证
function add(x, y) {
return x+y;
}
Ignition(点火):
负责将 AST 转换为 Bytecode,解释执行 Bytecode;同时收集 TurboFan 优化编译所需的信息,比如函数参数的类型;解释器执行时主要有四个模块,内存中的字节码、寄存器、栈、堆。
区别与 JVM,V8 是基于寄存器的解释器,JVM 是基于栈的解释器
栈解释器执行流程
基于栈的解释器,使用栈来保存中间变量、运算结果和参数等,下图是栈解释器的执行流程
public class Demo {
public static void main(String[] args) {
int a = 1;
int b = 2;
int c = (a + b) * 5;
}
}
基于寄存器的解释器
基于寄存器的解释器,支持对寄存器进行指令操作,将参数、中间计算结果放入寄存器中,需要运算是直接从内存地址中取进行计算(add a0,[0])
Turbofan
看名字知道要进入运行阶段了,对字节码进行编译成机器码,这里的机器码指的不是 CPU 的指令集,而是汇编代码,在汇编语言中,一行汇编代码表示一串指令。
三、关于 Java 中使用 V8 运行 JS
1. Js 执行 Java 代码
需要创建 V8 Runtime,进行注册 Java 方法以及 Js 调用名称
val runtime = V8.createV8Runtime()
runtime.registerJavaMethod({ receiver, params ->
if (params.length() > 0) {
val arg = params.get(0)
println("Js调用Java方法$arg")
if (arg is Releasable) {
arg.release()
}
}
}, "print")
runtime.executeScript("print('hello world');") // java执行js 代码
runtime.close()
2. Java 执行 Js 代码
java 读取 js 文件,可执行 js 文件中的代码
function add(x, y) {
print("增加了"+x+y);
return x + y;
}
val runtime = V8.createV8Runtime()
val source = Okio.buffer(Okio.source(context.assets.open("test.js")))
var script = source.readUtf8()
source.close()
runtime.executeScript(script)
runtime.executeScript("add(1,2);")
3. 小程序引擎
3.1. 初始化
小程序页面由 webView 进行渲染,V8 逻辑线程则创建处理小程序 page.js、app.js 等其他部分。
小程序由1个或者多个逻辑线程管理,当用户第一次打开小程序时,视图线程和逻辑线程会同时启动初始化,我将以下分为三个步骤:
框架初始化: 在启动一些小程序所需要的前置条件后(例如 appid 验证、用户信息获取、小程序资源包加载等),运行小程序全局生命周期 app.onLaunch 和 app.onShow 创建小程序示例。完成小程序的初适化,onShow 则代表小程序现在处在前台页面。
页面初始化: 由逻辑线程运行小程序页面生命周期 onLoad、onShow 等函数,创建小程序真实页面实例
通知渲染: 在上一步页面初始化完成后,已获得首屏页面加载所需数据;当视图线程初始化完成后通知逻辑线程后,由逻辑线程将初始化数据,执行 JsBridge 返回给视图线程
3.2. 渲染层->逻辑层 事件传递
上述条件完成了小程序引擎的初始化流程,我们获得了全局的 app 上下文,以及页面 page 上下文
用户在 webview 上点击、输入、滑动,渲染层接收到事件后,将事件名传递给逻辑层(例如:onQueryOrderList),逻辑层执行 js (runtime.executeScript("onQueryOrderList();")),执行完成后将数据再由 JsBridge 回传给渲染层。
3.3. 逻辑层->Native Api调用
在 V8 初始化时,会注册小程序所需要的函数方法,例如 js 中执行 wx.request,实际执行到 native 层的函数
val wx = V8Manage.v8.getObject("wx")
wx.registerJavaMethod({ receiver, parameters ->
val localPageId = receiver.getString("pageId")
val data = parameters?.getObject(0)
data?.add("pageId", localPageId)
data?.add("requestId", UUID.randomUUID().toString())
wx.getObject("requestData").add(data?.getString("requestId"), data)
}, "request")
转载自:https://juejin.cn/post/7232183128013701157