返回「计算机、信息技术与工程」

底层深入-什么是 运行时

底层深入 什么是 运行时

更多
Markdown 结构化数据
本文目录 21 个章节

底层深入-什么是 运行时

# 底层深入-什么是 运行时

什么叫“运行时”?

  1. 语言规范负责说“语义”——for 如何展开、异常如何传播等。

  2. 编译器/字节码生成器负责把源代码翻译成中间形式(JVM 字节码、CIL、Python bytecode、JS bytecode…)。

  3. 运行时 = 进程内那段常驻代码 + 数据区,在程序 执行期 解析/执行这些中间形式,并向宿主操作系统或宿主环境(Host)做系统调用。

    • 关键职责:

      • 装载(Class Loader / Metadata Loader / PyImport / Script Loader)
      • 执行引擎(解释器循环或 JIT/AOT 生成机器码)
      • 内存管理(堆、栈、垃圾回收或引用计数)
      • 运行时库(字符串、集合、反射、I/O)
      • 原生互操作(JNI、P/Invoke、C-API、Web IDL…)

几种运行时

Java 运行时:JVM

核心抽象

  • ClassLoader 子系统:按委托模型链式查找 .class → 填充方法区元数据。(GeeksforGeeks)
  • Runtime Data Area:线程私有 、程序计数器;共享 、方法区、运行时常量池。(FreeCodeCamp)
  • Execution Engine:包含解释器与 C2/C1 JIT;遇到热点方法即从字节码生成机器码。
  • GC:G1/ZGC/Serial GC 等通过写屏障维护分代堆。

伪代码:类加载 + 解释循环

byte[] bytes = readClassFile(name);
Class c = defineClass(bytes);        // → 方法区注册

while (pc < bytecode.length) {       // 解释器
    switch(bytecode[pc++]) {
        case IADD: stack.push(stack.pop() + stack.pop()); break;
        case INVOKEVIRTUAL:
            Method m = cp.resolveMethod(index);
            frameStack.push(new Frame(m));
            break;
        ...
    }
}

Mono 运行时(.NET CLR 的开源实现)

  • Metadata Loader 读取 ECMA-335 元数据表;同 JVM 的常量池。
  • JIT/Interpreter/AOT 三种模式可切换(iOS 可静态 AOT)。(DeepWiki, Mono Project)
  • GC:SGen(分代、并行压缩)或 libgc;托管对象由 write barrier 追踪引用。
  • P/Invoke:自动生成“托管→本地”桩代码让 C# 调 C 库。

伪代码(极简 JIT)

IR *ir = mono_method_to_ir(method);
Code *code = mono_codegen(ir);   // x64/arm64 等后端
void* entry = jit_alloc(code->size);
memcpy(entry, code->bytes, code->size);
method->entry = entry;

Python 运行时(CPython)

  • PyInterpreterState / PyThreadState 结构体保存全局与线程局部变量。(DeepWiki)
  • 字节码虚拟机:名为 ceval loop,对每条 op 做栈式运算。
  • 内存模型:PyObject 头部引用计数字段,循环引用靠分代 GC 回收。

伪代码:核心执行循环

for (;;) {
    int opcode = NEXTOP();
    switch(opcode) {
        case LOAD_FAST:  PUSH(fastlocals[i]);           break;
        case CALL_FUNCTION:
            PyObject *func = POP();
            PyObject *args = POP();
            PUSH(PyObject_Call(func, args, NULL));      break;
        ...
    }
}

ECMAScript 运行时 + Host

ECMAScript(简称 ES)是一部由 Ecma International 制定的脚本语言规范,文件编号 ECMA-262。它定义了语言的语法、类型系统、内建对象、错误模型、并发抽象(Promise Jobs)等核心语义,用来保证不同实现之间可以运行同一段脚本代码。

  • 1995 年 Brendan Eich 在 Netscape 浏览器中实现了早期的 LiveScript/JavaScript
  • 为了避免与 Sun 的“Java”商标纠纷,1996 年 Netscape 把这门语言送交标准机构 ECMA,双方同意用 “ECMAScript” 作为标准名称,浏览器实现继续对外叫 “JavaScript”。

语言引擎

  • 解析 → 生成 AST/IR → 点燃 JIT(Ignition/TurboFan、LLInt/DFG JIT…)。
  • 垃圾回收:Oilpan(V8)、Mark-Sweep/Mark-Compact。

Host(浏览器 / Node / WeChat Game Runtime)

伪代码:HostPromiseRejectionTracker

function HostPromiseRejectionTracker(p, op) {
  if (op === 'reject') {
    queueMacroTask(() => {
      if (!p.handled) dispatch('unhandledrejection', {reason: p.result});
    });
  } else if (op === 'handle') {
    p.handled = true;
  }
}

微信小游戏 Runtime 在 C++ / Java 层实现 queueMacroTask,把任务压到主线程尾,再经 JSCore/V8 调度(与浏览器概念相同,只是没有 DOM)。

运行时实现

V8 由 Google 于 2008 年为 Chrome 开发,名字源于 “V-8” 引擎,寓意高性能。

  • 它同时实现 ECMAScriptWebAssembly,被 Node.js、Deno、Electron、微信小游戏(Android)等运行时复用,成为最广泛部署的 ES 引擎之一。

主流 JavaScript 引擎与来源

引擎 主要维护方 首次发布 名称由来 / 定位
V8 Google 2008 命名取自 V-8 汽车发动机,强调“高性能”
SpiderMonkey Mozilla 1996 Brendan Eich 首个 JS 实现,被火狐延续至今
JavaScriptCore Apple / WebKit 2002 Safari 内置;别名 Nitro/SquirrelFish;同样实现 ECMAScript
Hermes Meta 2019 React Native 专用,面向移动端低内存场景
QuickJS Fabrice Bellard 2019 超小型、支持最新 ES 特性,常嵌入 IoT

把四种运行时放在一张脑图里

  • 装载器:ClassLoader / .NET MetadataLoader / PyImport / ScriptLoader
  • 执行引擎:解释器 & JIT (+AOT)
  • 内存管理:GC(分代、压缩、并行)、引用计数
  • 宿主接口:JNI / P-Invoke / C-API / window.* wx.*

运行时就是“让字节码真正跑起来”的那整套代码与数据区; 编写它意味着:写 Parser/JIT/GC、做平台抽象层、暴露宿主 API,并持续调优内存与性能。理解了这些通用部件,再看任何语言的“runtime”文档就能迅速对号入座。

拓展延申:原生互操作的实现

  • 原生互操作(JNI、P/Invoke、C-API、Web IDL…)

以JNI和P/Invoke为例进行说明:

步骤 JNI P/Invoke
① 托管端声明 native 关键字写进 .class → ClassFile 标志 ACC_NATIVE extern + [DllImport] 写进 IL → 方法标志 pinvokeimpl
② 运行时解析 System.loadLibrary() 触发 动态装载;JVM 用 dlsym/GetProcAddressJava_pkg_Class_meth 或注册表 CLR 在 JIT 时查看元数据→调用 LoadLibrary/dlopen & GetProcAddress
③ 桩代码生成 JVM 按平台 ABI 生成 native stub:• 入栈 JNIEnv* + jobject + 参数• 建立 JNI本地帧 屏蔽 GC CLR 生成 IL Stub(.NET 7 起可用 Source Generator,提前产出 C#→CIL):• 参数封送(marshal)• 线程进入 Preemptive GC 模式
④ 跨界调用 C 函数里通过 JNIEnv* 表调用 JVM 服务;返回时释放本地引用 C 函数直接按 C 调用约定返回;IL Stub 再把结果封送回托管对象
⑤ 异常/GC 协作 C 抛异常 ⇒ (*env)->ThrowNew 映射到 Java;GC 不会移动 JNI 本地引用 C 侧报错通过 SetLastError,托管层 [DllImport(SetLastError=true)] 捕获;GC 期间 stub 自动 pin/unpin

0. 设计总览

┌─────────┐      ┌───────────────┐      ┌─────────────┐
│  元数据 │──┐──▶│  延迟解析桩   │──┐──▶│   真正桩    │──▶ 原生函数
└─────────┘  │   └───────────────┘  │   └─────────────┘
             │                      │
             ▼                      ▼
  类加载阶段处理 ACC_NATIVE   首次调用时 patch entry

核心链路: 标记 native → 延迟解析 → 找符号 → 生成/缓存桩 → 线程 & GC 保护 → 参数封送 → 调原生


1. 元数据加载阶段

任务 JNI (HotSpot) P Invoke (CoreCLR) 交付物
解析“这是 native 方法” ACC_NATIVE 写进 Method* pinvokeimpl 写进 MethodDesc 修改 Class/Type Loader,支持 native 标志
保存额外属性 无(符号名通过约定推算) DllImport 属性:库名、调用约定、SetLastError… 运行时元数据表 中新增字段

2. 符号解析 & 动态库管理

步骤 JNI P Invoke 要点
触发时机 System.loadLibrary() 或第一次调用 JIT 初次触发 P Invoke 都选 懒加载;失败时抛高层异常
动态装载 dlopen/LoadLibrary 同上 维护 NativeLibraryCache,库句柄唯一
符号名规则 Java_{pkg}_{Class}_{method}__{sig} 替换 / → _,_ → _1 函数名直接写在 DllImport 提供 NameManglingUtil(可配置)
Patch 回调地址 Method*::set_native_function MethodDesc::SetNDirectTarget 确保线程安全,双检 + 锁或 CAS

3. 桩(Stub)生成器

3.1 生成策略

维度 JNI P Invoke 建议
生成时机 每个 native 方法一次(或 AOT 缓存) JIT 一次,或 .NET 7 起 Source Gen 先做 即时生成,后期再做 AOT
目标 按平台 ABI & GC 协议包装 生成 IL,再由 JIT 编译 统一走 StubEmitter:C++ 模板 + MacroAssembler
性能阶梯 普通 JNI → Fast JNI → Critical Native 普通 P Invoke → Stub 内联 → 直接调用 暴露“Fast 调用”注解供高级用户

3.2 桩内容模板(伪代码)

; prologue
push rbp
mov  rbp, rsp
sub  rsp, local_size
call ThreadState_EnterNative   ; 切换到 native / Preemptive GC

; 参数封送
mov rdx, [rbp+arg0]            ; e.g. jint → int32
; ...

; 调用原生符号
call [resolved_addr]

; 返回值封送 / 异常映射
call ThreadState_LeaveNative
mov  rsp, rbp
pop  rbp
ret

4. 封送 (Marshalling) 引擎

类型类别 JNI API P Invoke Attribute / API 关键代码
基元 直接复制 直接复制
字符串 GetStringUTFChars / NewStringUTF [MarshalAs(UnmanagedType.LPUTF8Str)] StringMarshaller
数组 GetArrayElements fixed / GCHandle pin PinnedArrayPool
结构体 手写 GetFieldID + byte copy StructLayout(LayoutKind.Sequential) 解析 C# 布局元数据生成拷贝表
异常 ThrowNew, ExceptionCheck SetLastError=true, Marshal.GetLastWin32Error() ErrorMapper

5. 线程 & GC 协作

事项 JNI P Invoke 实现要求
线程附加 AttachCurrentThread / Detach 自动 维护 ThreadLocal JNIEnv / GCMode
GC 安全点 Pin 本地引用;本地帧 Preemptive vs Cooperative 在桩的 prologue / epilogue 标记状态
反向调用 Call<type>Method Delegate → 函数指针 (GCHandle) 生成 Reverse Stub,确保再次切换 GC 模式