源码是如何变成可执行文件的(gcc版)

GCC编译

源码是如何变成可执行文件的(gcc版)

C语言生成可执行程序一共有4个步骤:预处理 → 编译 → 汇编 → 链接,每一步都能单独执行。咱们用下面的简单例子,讲解一下整个编译过程。

测试代码(test.c)

#include <stdio.h>
#define MSG "Hello, C Process!"

int main() {
    printf("%s\n", MSG);
    return 0;
}

第一步:预处理(Preprocessing)

命令

gcc -E test.c -o test.i

输入
test.c(我们写的C语言源码,文本格式)

输出
test.i(展开后的纯C代码,文本格式,可直接用vim/gedit打开),其体积会大幅增大,通常从几十行变成几万行,核心原因是插入了头文件内容。

核心工作
1. 展开 #include 头文件:把 (系统头文件,路径通常在 /usr/include/)里的所有内容,直接复制粘贴到 test.i 中,这是 test.i 体积变大的核心原因。

2. 展开 #define 宏定义:纯文本替换,把代码中所有的 MSG,全部替换成 “Hello, C Process!”,替换后宏名 MSG 会消失。

3. 删除所有注释:// 单行注释、/* */ 多行注释,全部删除,不保留任何注释内容,预处理只保留有效代码。

4. 处理条件编译:如果代码中有 #if、#ifdef、#else、#endif 等,会根据条件保留对应代码、删除无用代码(比如调试用的代码,可通过条件编译屏蔽)。

5. 添加行号和文件名标记:在代码中插入隐藏的行号、文件名信息(比如 # 1 “test.c”),方便后续编译报错时,快速定位到源码中的错误位置。

预处理阶段不检查任何C语言语法错误,哪怕你把 printf 写成 printff,这一步也不会报错,因为它只做“文本替换/删除”,不识别C语言语法。而且 test.i 仍然是纯C语言代码,不是汇编、不是二进制,打开后能看懂,只是行数极多,大部分是展开的头文件内容。实操中,用 head -20 test.i 可以快速查看 test.i 的前20行,能直观看到头文件展开和宏替换的效果,不用打开整个大文件。

第二步:编译(Compilation)

命令

gcc -S test.i -o test.s

输入
test.i(预处理后的纯C代码)

输出
test.s(汇编语言代码,文本格式,可直接打开查看),其内容与CPU架构强相关,同样的 test.i 文件,在 x86 电脑(比如普通笔记本)和 ARM 电脑(比如树莓派)上,生成的 test.s 内容完全不同,因为两种CPU的指令集不一样。

核心工作
1. 检查C语言语法错误:这是第一个真正检查语法的阶段,也是整个流程中首次进行语法校验的环节。如果代码有少分号、括号不匹配、变量未定义、函数调用错误等,都会在这一步报错,终止流程(比如把 main 写成 mian,会报“未定义的引用 to main”)。若此处报错,只需要回到 test.c 中修改语法错误,重新执行预处理和编译即可,不用重新执行后续步骤。

2. 语义分析与优化:编译器会分析代码的逻辑(比如变量的作用域、函数的调用关系),并做基础优化(默认无优化,加 -O2 参数可开启中级优化,让代码运行更快、体积更小)。

3. 翻译C代码→汇编代码:把C语言的语句(比如 printf、return 0),翻译成对应CPU架构的汇编指令(比如 x86 架构的 mov、call 指令)。这一步才是真正的“编译”,预处理只是“文本处理”,而编译是“语言转换”,把高级C语言转换成低级汇编语言。

第三步:汇编(Assembly)

命令

gcc -c test.s -o test.o

输入
test.s(汇编语言代码)

输出
test.o(二进制目标文件,不可直接阅读,需用 objdump 工具查看),需要注意的是,test.o 并不能直接运行,运行会报错“Permission denied”或“无法执行二进制文件”。

核心工作
1. 汇编指令→机器码:把 test.s 中的汇编指令,一一翻译成CPU能直接识别的二进制代码(0和1的组合),这是代码从“人类可看懂”到“机器可识别”的关键一步。

2. 生成符号表:记录代码中的函数名、变量名(比如 main、printf),以及它们在目标文件中的临时位置(此时还不是最终内存地址)。

3. 生成重定位信息:标记出“需要后续修补地址”的位置(比如 printf 函数,此时只知道要调用它,但不知道它在内存中的具体地址,需要链接阶段修补)。

test.o 无法直接运行的原因有3个:一是函数地址未确定,printf 等库函数的真实地址还没分配,程序不知道去哪里找这个函数;二是没有程序入口信息,系统不知道从哪里开始执行(虽然有 main 函数,但还没和系统的启动代码关联);三是未符合 Linux 可执行文件格式(ELF),缺少程序头、段信息等,系统无法识别它是可执行程序。实操中,用 objdump -d test.o 可以查看 test.o 中的机器码和汇编指令,能看到 main 函数对应的二进制代码。如果有多个源码文件,比如 test1.c、test2.c,分别汇编后会生成 test1.o、test2.o,后续链接时会合并这两个目标文件。

第四步:链接(Linking)

命令

gcc test.o -o test

(底层实际调用 ld 链接器,gcc 只是封装了这个过程,直接用 ld test.o -o test 也能链接,但需要手动指定库路径,不推荐,用 gcc 链接更便捷,它会自动处理库路径和启动代码,不用手动配置)

输入
test.o(目标文件) + 系统共享库(主要是 libc.so,C标准库,包含 printf 等函数的实现) + 系统启动代码(crt0.o 等,负责初始化程序、调用 main 函数)

输出
test(最终可执行文件,Linux 下默认是 ELF 格式,绿色文件,可直接运行)。Linux 下的可执行文件、目标文件、共享库,都是 ELF 格式,用 file test 可以查看文件格式(会显示“ELF 64-bit LSB executable”)。

核心工作
1. 合并目标文件:如果有多个 .o 文件(比如 test1.o、test2.o),会把它们合并成一个文件,统一分配内存地址。

2. 符号解析:找到代码中引用的外部符号(比如 printf),在系统库(libc.so)中找到对应的实现,建立关联。

3. 重定位:根据符号的真实地址,修补目标文件中“未确定的地址”(比如把 printf 的调用地址,替换成 libc.so 中 printf 的实际内存地址)。

4. 封装 ELF 格式:把合并后的机器码、符号表、重定位信息等,打包成 Linux 可识别的 ELF 可执行文件格式,添加程序头(告诉系统如何加载程序)、段信息(.text 代码段、.data 数据段、.bss 未初始化数据段)。

5. 关联启动代码:把系统启动代码(crt0.o)和我们的 main 函数关联,程序运行时,先执行启动代码(初始化栈、堆、环境变量),再调用 main 函数,main 函数结束后,由启动代码处理返回值。

链接分为动态链接和静态链接两种,需重点区分,实操中经常用到:

– 动态链接(默认):程序运行时,才去加载 libc.so 共享库,如果系统中没有 libc.so,程序会报错“找不到共享库”;优点是程序体积小,多个程序可以共用一个 libc.so,节省内存。实操命令(显式指定动态链接):gcc test.o -o test -ldl

– 静态链接:把 libc.so 中的相关代码,直接打包进可执行文件中,程序运行时不需要依赖系统中的 libc.so,可独立运行(比如拷贝到没有安装C标准库的Linux系统中也能运行);优点是可移植性强,缺点是程序体积大,这是正常现象,静态链接会打包整个库,比如 test 可能从几KB变成几MB。实操命令(静态链接):gcc test.o -o test -static(需要系统安装静态库,比如 libc.a,否则会报错)

链接阶段若报错“未定义的引用 to xxx”,大概率是两个原因:① 代码中调用的函数没有实现(比如自己写了一个函数声明,没写实现);② 没有链接对应的库(比如用了 math 库的 sqrt 函数,需要加 -lm 参数链接 math 库)。

最终运行与验证

./test

输出结果:Hello, C Process!,说明整个流程成功。

最终总结

test.c(源码,文本)
  ↓(预处理 gcc -E)
test.i(展开后C代码,文本)
  ↓(编译 gcc -S)
test.s(汇编代码,文本)
  ↓(汇编 gcc -c)
test.o(目标文件,二进制,不可运行)
  ↓(链接 gcc/ld)
test(可执行文件,ELF格式,可运行)

Linux 下 C 源码到可执行文件,核心就是“4步走”,每一步都有明确的目标和输出,没有神秘操作:

1. 预处理:处理文本,把“不完整”的源码补全;

2. 编译:检查语法,把高级语言转成低级汇编;

3. 汇编:翻译指令,把汇编转成机器能识别的二进制;

4. 链接:整合资源,把半成品变成能直接运行的程序。

大家在日常工作中,有遇到哪些编译相关的问题呢?欢迎留言讨论

十大主流程序虚拟机深度解析:从架构到选型,一文看透PVM核心技术

程序虚拟机


十大主流程序虚拟机深度解析:从架构到选型,一文看透PVM核心技术

在现代软件开发中,程序虚拟机(PVM)是连接高级语言与底层硬件的核心桥梁,它不仅实现了“一次编译,到处运行”的跨平台梦想,更在不同场景下(企业级后端、前端、移动端、嵌入式等)承担着性能优化、资源管控、安全隔离的关键角色。

很多开发者对虚拟机的认知停留在“HotSpot=Java虚拟机”“V8=JS引擎”的表层,却忽略了它们背后截然不同的架构设计、编译策略和优化逻辑。今天,我们就来拆解十大主流虚拟机(HotSpot、V8、CLR、ART、Zend、PyPy、LuaJIT、BEAM、Wasmtime、GraalVM),从核心架构、JIT编译、内存管理、并发模型到生态选型,一文讲透虚拟机的技术本质与实战价值。

一、先理清基础:虚拟机的两大核心分类

在深入分析之前,我们先明确一个关键区分:虚拟机并非单一概念,主要分为两类,本文重点聚焦后者——程序虚拟机:

1、系统虚拟机:模拟完整的硬件环境(CPU、内存、IO等),如VMware、VirtualBox,本质是“硬件虚拟化”,用于运行完整的操作系统,隔离性强但开销较大。

2、程序虚拟机(引擎、语言运行时、进程虚拟机、语言虚拟机):不模拟硬件,而是执行高级语言编译后的中间代码(字节码、IR),核心作用是实现跨平台、内存自动管理和语言抽象,如HotSpot、V8等,开销小、针对性强,也是我们日常开发中接触最多的类型。

本文分析的十大虚拟机,均属于程序虚拟机,它们虽目标一致,但针对不同场景做了极致优化,形成了各自独特的技术路线。

二、核心维度拆解:十大虚拟机底层技术对比

要看透虚拟机的差异,我们从核心架构、JIT编译、内存管理、并发模型、运行时生态5个核心维度,进行全方位拆解,先通过一张表格快速建立整体认知,再逐一深入细节。

(一)核心架构对比

架构类型直接决定了虚拟机的执行效率、内存开销和适用场景,主要分为“栈式虚拟机”和“寄存器虚拟机”两大类,各有优劣:

虚拟机 架构类型 执行模型 核心设计哲学
HotSpot 栈式虚拟机 + 寄存器优化 字节码解释 + 分层JIT(C1/C2) 一次编写到处运行,企业级稳定性、可观测性优先
V8 寄存器机 + 隐藏类对象模型 Ignition解释器 + TurboFan JIT 启动速度与峰值性能平衡,Web交互、低延迟优先
CLR 栈式虚拟机 IL解释 + RyuJIT分层编译 语言互操作、工程化、类型系统极致设计
ART 栈式虚拟机(Dex) AOT+JIT混合,Profile引导优化 移动设备功耗、内存、流畅度深度优化
Zend 栈式虚拟机 Opcode解释 + OPcache缓存 Web短请求、Share-Nothing、用完即释放
PyPy 元追踪JIT架构 Meta-Tracing JIT 动态语言性能极限,兼容CPython
LuaJIT 寄存器机 Trace-JIT 追踪编译器 极致轻量、嵌入友好、接近C语言效率
BEAM 寄存器机(1024个X寄存器) 解释执行 + 现代JIT Actor模型、软实时、容错、不共享内存、热更新
Wasmtime 栈式虚拟机(紧凑二进制) 多模式:解释/JIT/AOT 强沙箱、通用跨平台、近原生性能、安全隔离
GraalVM 多语言抽象架构 Truffle AST + Graal JIT 多语言共生、云原生、Native Image 无VM启动

关键总结:栈式虚拟机(HotSpot/CLR/Zend)代码简洁、跨平台性更强;寄存器虚拟机(V8/BEAM/LuaJIT)执行效率更高、内存开销更小,更适合性能敏感场景。而GraalVM则打破了单一架构限制,实现了多语言的统一运行时。

(二)JIT编译技术:虚拟机性能的核心引擎

对于程序虚拟机而言,JIT(即时编译)是提升执行性能的关键——它能将中间代码动态编译为机器码,兼顾解释执行的灵活性和编译执行的高效性。不同虚拟机的JIT策略差异巨大,直接决定了其性能表现:

1. 十大虚拟机JIT策略对比

虚拟机 JIT类型 触发策略 优化特点
HotSpot 分层Method-JIT 方法计数+回边计数 C1快速/C2深度,OSR栈上替换,逃逸分析
V8 方法JIT+流图优化 类型反馈驱动 隐藏类+内联缓存,标量替换,去优化
CLR Method-JIT(RyuJIT) 方法热度+分层 SIMD向量化,硬件intrinsic,内存布局优化
ART 混合JIT+后台AOT 采样+Profile 安装/后台异步优化,不影响前台流畅
Zend Opcode解释+OPcache缓存(无独立JIT) 请求触发缓存 轻量优化,适配Web短请求,无需复杂JIT
PyPy Meta-Tracing 循环热路径追踪 类型特化、分配消除、跨层优化
LuaJIT Trace-JIT 循环热计数 线性IR,极简代码生成,极致短小
BEAM 现代JIT(OTP24+) 解释为主 追求确定性延迟,不做激进优化
Wasmtime JIT+预编译(默认JIT,支持AOT预编译) 预编译/JIT按需触发 边缘场景AOT,零冷启动,安全沙箱,WASI标准支持
GraalVM 全功能Graal JIT 推测+部分求值 去虚拟化、跨语言内联、Native Image

2. 两大特色JIT机制解析(PyPy & LuaJIT)

在所有JIT策略中,PyPy的Meta-Tracing和LuaJIT的Trace-JIT最为独特,也是动态语言性能优化的典范:

PyPy的Meta-Tracing JIT:区别于传统Tracing JIT“直接追踪用户代码”,它通过“追踪解释器的执行行为”,自动生成用户代码的优化机器码,核心优势是“自动类型特化”和“跨抽象层优化”,能让Python代码在计算密集场景下提速10~100倍。但存在“性能悬崖”问题——当类型假设失效时,会立即回退到解释器,性能波动较大。
传统Tracing JIT: 用户代码 → 记录热点路径 → 编译机器码
PyPy Meta-Tracing: 解释器执行 → 追踪解释器行为 → 自动生成用户代码JIT

LuaJIT的Trace-JIT:被誉为“动态语言JIT的杰作”,它不编译整个方法,而是追踪代码的热执行路径(尤其是循环),将线性路径编译为极致优化的机器码,配合FFI(外部函数接口),能实现“零开销调用C语言”,性能接近C语言,且虚拟机体积仅200KB,是嵌入式场景的首选。

3. 内存管理与GC:虚拟机稳定性的关键

内存管理(尤其是垃圾回收GC)直接决定了虚拟机的稳定性、延迟和资源开销——对于长生命周期的应用(如企业后端),GC的性能的至关重要;对于资源受限场景(如移动端、嵌入式),内存开销则是核心考量。

虚拟机 内存模型 GC算法 特色机制
HotSpot 分代/区域化堆 G1/ZGC/Shenandoah 亚毫秒停顿,TB级堆,区域化回收
V8 分代+增量 Scavenge + 标记压缩 Orinoco并发GC,主线程几乎无停顿
CLR 托管堆+LOH大对象堆 分代0/1/2 后台GC,Span零拷贝,值类型优化
ART 移动优化堆 Concurrent Copying 读屏障优先,省电,低内存碎片
Zend 请求生命周期内存 引用计数+周期回收 请求结束全释放,无内存泄漏累积
PyPy 分代+增量GC 标记清除 写屏障优化,内存压缩,无GIL额外停顿
LuaJIT 轻量堆 增量标记清除 可手动控制,极低开销,实时友好
BEAM 进程私有独立堆 进程局部GC 无全局STW,GC只影响单个Actor
Wasmtime 线性内存(Linear Memory) 无内置GC(可集成外部GC,如Boehm GC) 沙箱隔离,内存由宿主/语言管理,支持内存安全校验
GraalVM 统一堆+原生镜像 HotSpot GC / 无GC Native Image可完全去掉GC

核心亮点:BEAM的内存管理是“独一档”的存在——每个Actor(轻量进程)拥有独立的私有堆,GC仅暂停当前进程,全局无STW(Stop-The-World)停顿,这也是它能实现“百万级并发”和“软实时”的核心原因;而GraalVM的Native Image则彻底打破了“虚拟机必须有GC”的固有认知,通过AOT编译将Java应用转为原生可执行文件,实现无GC运行,大幅降低内存开销。

4. 并发模型:应对高并发的底层逻辑

随着分布式、高并发场景的普及,虚拟机的并发模型直接决定了其应对高负载的能力。不同虚拟机的并发设计,完全围绕其核心应用场景展开:

虚拟机 并发原语 调度模型 特色能力
HotSpot 内核线程(1:1)+虚拟线程 OS调度 Project Loom 高并发,结构化并发
V8 单线程事件循环+Worker 事件驱动 无锁JS主线程,Isolates隔离
CLR 线程+Task+async/await OS调度 线程池,并行库,异步生态最成熟
ART 线程+Handler/Looper OS调度 Android 主线程UI模型,Binder IPC
Zend FPM多进程 OS进程 Share-Nothing,请求级隔离
PyPy 线程+GIL OS线程 计算加速,但仍受GIL限制
LuaJIT 协程(coroutine) 协作式 C无缝调用,极小开销,嵌入首选
BEAM Actor轻量进程 M:N 抢占式调度 百万进程,监督树,分布式,热更新
Wasmtime Wasm线程+原子操作 宿主调度(支持多线程调度优化) 共享线性内存,原子操作,无数据竞争,支持WASI并发标准
GraalVM 多语言抽象 宿主线程 跨语言线程安全,共享堆

划重点:

BEAM的Actor模型:单节点可支撑百万级轻量进程,进程间不共享内存,通过消息传递通信,配合“Reduction计数”抢占式调度,实现软实时和高容错,是电信系统、IM、消息推送等场景的不二之选。

V8的单线程事件循环:虽然JS主线程是单线程,但通过事件驱动和Web Worker隔离,实现了非阻塞I/O,支撑了浏览器和Node.js的高并发场景。

HotSpot的虚拟线程(Project Loom):打破了“1:1线程模型”的限制,实现了“百万级虚拟线程”,大幅降低高并发场景下的线程开销,让Java在微服务场景更具优势。

5. 运行时特性与生态:落地场景的核心支撑

虚拟机的价值最终要落地到具体场景,而运行时特性(启动速度、多语言支持)和生态完善度,直接决定了其适用范围和开发效率:

虚拟机 启动模式 多语言支持 典型应用场景
HotSpot JIT偏慢,AOT(Graal)快 Java/Kotlin/Scala/Groovy 企业后端、大数据、中间件
V8 快照快速启动 JS/TS/Wasm 浏览器、Node.js、边缘函数
CLR JIT适中 C#/F#/VB.NET 全栈、Unity、Windows、服务端
ART 安装/后台优化 Java/Kotlin Android 应用
Zend OPcache加速 PHP Web快速开发、CMS、中小型后台
PyPy 启动略慢 Python Python计算密集、长时运行服务
LuaJIT 秒启动(200KB) Lua 嵌入式、游戏脚本、高性能网关
BEAM 字节码快速加载 Erlang/Elixir 高并发长连接、高可用分布式系统
Wasmtime 极快加载(毫秒级) C/C++/Rust/Go(编译为Wasm字节码) 边缘计算、插件系统、安全沙箱
GraalVM Native镜像毫秒启动 全语言支持 多语言微服务、Serverless、云原生

三、各虚拟机核心特色总结

结合上述维度分析,我们提炼出每款虚拟机的“核心竞争力”,帮你快速抓住其本质,为技术选型提供参考:

HotSpot:企业级标杆

1、核心优势:25年生产环境验证,生态最完善(企业后端、大数据、中间件全覆盖),GC家族丰富(从吞吐量优先的G1到低延迟的ZGC/Shenandoah),可观测性极强(JMX、JFR等工具链成熟)。

2、近期突破:虚拟线程(Loom)解决高并发线程开销问题,Valhalla项目引入值类型,消除装箱开销。

3、短板:JIT启动速度较慢(可通过GraalVM AOT弥补),内存开销较大。

V8(Chrome/Node.js引擎):前端与Node.js核心

1、核心优势:动态语言JIT的标杆,通过“隐藏类+内联缓存”将JS性能提升至接近静态语言,Orinoco GC保证Web交互低延迟,与Wasm无缝互操作,支撑浏览器、Node.js、Electron等全场景。

2、短板:单线程模型无法利用多核CPU的全部性能(需通过Worker弥补)。

CLR(Common Language Runtime):强类型工程化典范

1、核心优势:CTS通用类型系统实现多语言无缝互操作,与Windows深度集成,RyuJIT编译器的SIMD向量化和硬件优化极强,async/await异步模型成熟,Span实现托管环境零拷贝。

2、短板:早期生态局限于Windows,目前已通过.NET Core实现全平台,但生态成熟度略逊于HotSpot。

ART(Android Runtime):移动端专属优化

1、核心优势:专为移动设备优化,采用“安装时AOT+运行时JIT+Profile引导”的混合编译策略,兼顾安装速度与运行流畅度,Concurrent Copying GC省电、低内存碎片,Zygote预加载加速启动。

2、短板:仅适用于Android系统,无跨平台能力。

Zend Engine:Web快速开发神器

1、核心优势:Share-Nothing架构,请求级隔离,请求结束即释放全部内存,无内存泄漏累积,OPcache加速字节码执行,开发效率极高,适配Web短请求场景。

2、短板:运行时性能一般,不适合计算密集型场景。

PyPy:Python性能救星

1、核心优势:Meta-Tracing JIT自动优化Python代码,长时运行的计算密集型任务性能远超CPython(平均提速4-5倍,最高100倍),分代GC解决CPython的循环引用问题。

2、短板:C扩展兼容性不如CPython,启动速度略慢。

LuaJIT:嵌入式与网关首选

1、核心优势:极致轻量(200KB左右运行时),Trace-JIT编译实现接近C语言的性能,FFI零开销调用C语言,嵌入友好,是游戏脚本、OpenResty网关、嵌入式设备的首选。

2、短板:生态较小,仅支持Lua语言。

BEAM(Erlang/Elixir VM):高并发高可用王者

1、核心优势:Actor模型+消息传递,单节点百万级轻量进程,无全局GC停顿,支持热代码升级和容错监督树,分布式透明,满足电信级99.999%可用性要求。

2、短板:单线程性能一般,不适合计算密集型场景。

Wasmtime(WebAssembly Runtime):跨平台安全沙箱

1、核心优势:强沙箱安全模型,线性内存隔离,接近原生性能,体积小、加载快,支持WASI标准,可脱离浏览器运行于边缘、嵌入式、云沙箱场景,是多语言跨平台的通用目标。

2、短板:无内置GC(需依赖宿主语言),目前生态仍在完善中。

GraalVM:云原生多语言统一 runtime

GraalVM:云原生多语言统一 runtime:Truffle框架让解释器自动获得JIT能力,支持Java、JS、Python等多语言零开销互操作,Native Image实现毫秒级启动和极低内存占用,是云原生、Serverless、多语言微服务的优选解决方案。

四、实战选型决策矩阵

结合场景需求,整理出最实用的选型建议,帮你快速匹配最合适的虚拟机:

场景需求 推荐虚拟机 核心理由
高并发长连接、高可用分布式系统 BEAM Actor模型、无全局GC、热更新、容错,单节点可支撑百万级并发
浏览器/前端生态、Node.js后端 V8 JS标准实现、Wasm支持、事件驱动,低延迟交互
企业级后端、大数据、微服务 HotSpot 生态成熟、GC稳定、可观测性强,工具链完善
Windows生态、Unity游戏、强类型工程 CLR 系统级集成、async/await异步、值类型优化
Android移动应用开发 ART 移动端功耗、内存、流畅度最优,原生支持
边缘计算、插件系统、安全沙箱 Wasmtime 轻量、跨平台、强隔离、接近原生性能,适配多场景沙箱需求
Python计算密集、长时运行服务 PyPy JIT加速显著,兼容CPython主流生态,适配计算密集场景
嵌入式、游戏脚本、高性能网关 LuaJIT 极小体积、极高性能、FFI零开销调用C,嵌入场景适配性强
多语言微服务、Serverless、云原生 GraalVM Native Image秒启动、多语言互操作、低内存,适配云原生场景
Web快速开发、CMS、中小型后台 Zend 开发效率高、部署简单、请求隔离无内存泄漏,适配中小型Web场景

五、总结:没有最好的虚拟机,只有最适合的场景

从HotSpot的企业级稳定,到V8的前端性能,再到BEAM的高并发、GraalVM的多语言统一,十大虚拟机的技术路线差异,本质上是“场景需求”的差异——它们没有绝对的优劣,只有对特定场景的适配度高低。

理解虚拟机的核心维度(架构、JIT、GC、并发、生态),不仅能帮助我们做出更合理的技术选型,更能让我们深入理解高级语言的运行机制,写出更高效、更稳定的代码。

最后,记住一个核心原则:选型的本质是“匹配场景”——企业后端优先HotSpot,前端/Node优先V8,高并发分布式优先BEAM,云原生多语言优先GraalVM,嵌入式优先LuaJIT,Web快速开发优先Zend,按需选择,才能发挥虚拟机的最大价值。

如果觉得本文对你有帮助,欢迎点赞、收藏,也可以在评论区留言讨论你在使用虚拟机时遇到的问题和经验~

V8引擎常用命令

1、测试文件test.js

function log(msg){
    console.log(msg)
}

hi = "hello"
log(hi)

2、输出AST语法树

d8 --print-ast test.js
[generating bytecode for function: ]
--- AST ---
FUNC at 0
. KIND 0
. LITERAL ID 0
. SUSPEND COUNT 0
. NAME ""
. INFERRED NAME ""
. DECLS
. . FUNCTION "log" = function log
. EXPRESSION STATEMENT at 47
. . ASSIGN at 50
. . . VAR PROXY unallocated (0000022CC5A21D98) (mode = DYNAMIC_GLOBAL, assigned = true) "hi"
. . . LITERAL "hello"
. EXPRESSION STATEMENT at 61
. . ASSIGN at -1
. . . VAR PROXY local[0] (0000022CC5A21CC8) (mode = TEMPORARY, assigned = true) ".result"
. . . CALL
. . . . VAR PROXY unallocated (0000022CC5A21B10) (mode = VAR, assigned = true) "log"
. . . . VAR PROXY unallocated (0000022CC5A21D98) (mode = DYNAMIC_GLOBAL, assigned = true) "hi"
. RETURN at -1
. . VAR PROXY local[0] (0000022CC5A21CC8) (mode = TEMPORARY, assigned = true) ".result"

[generating bytecode for function: log]
--- AST ---
FUNC at 12
. KIND 0
. LITERAL ID 1
. SUSPEND COUNT 0
. NAME "log"
. PARAMS
. . VAR (0000022CC5A21B68) (mode = VAR, assigned = false) "msg"
. DECLS
. . VARIABLE (0000022CC5A21B68) (mode = VAR, assigned = false) "msg"
. EXPRESSION STATEMENT at 24
. . CALL
. . . PROPERTY at 32
. . . . VAR PROXY unallocated (0000022CC5A21D18) (mode = DYNAMIC_GLOBAL, assigned = false) "console"
. . . . NAME log
. . . VAR PROXY parameter[0] (0000022CC5A21B68) (mode = VAR, assigned = false) "msg"

hello

3、输出作用域

d8 --print-scopes test.js
Inner function scope:
function log () { // (000001E067B50EA8) (12, 43)
  // 2 heap slots
  // local vars:
  VAR msg;  // (000001E067B54E18) never assigned
}
Global scope:
global { // (000001E067B50C88) (0, 68)
  // will be compiled
  // 1 stack slots
  // temporary vars:
  TEMPORARY .result;  // (000001E067B51288) local[0]
  // local vars:
  VAR log;  // (000001E067B510D0)
  // dynamic vars:
  DYNAMIC_GLOBAL hi;  // (000001E067B51358)

  function log () { // (000001E067B50EA8) (12, 43)
    // lazily parsed
    // 2 heap slots
  }
}
Global scope:
function log (msg) { // (000001E067B50EA8) (12, 43)
  // will be compiled
  // local vars:
  VAR msg;  // (000001E067B51128) parameter[0], never assigned
}
hello

4、输出字节码

d8 --print-bytecode test.js
[generated bytecode for function:  (0x03970824fc39 <SharedFunctionInfo>)]
Parameter count 1
Register count 3
Frame size 24
         000003970824FCEA @    0 : 12 00             LdaConstant [0]
         000003970824FCEC @    2 : 26 fa             Star r1
         000003970824FCEE @    4 : 27 fe f9          Mov <closure>, r2
         000003970824FCF1 @    7 : 61 37 01 fa 02    CallRuntime [DeclareGlobals], r1-r2
         000003970824FCF6 @   12 : 12 01             LdaConstant [1]
         000003970824FCF8 @   14 : 15 02 00          StaGlobal [2], [0]
         000003970824FCFB @   17 : 13 03 02          LdaGlobal [3], [2]
         000003970824FCFE @   20 : 26 fa             Star r1
         000003970824FD00 @   22 : 13 02 04          LdaGlobal [2], [4]
         000003970824FD03 @   25 : 26 f9             Star r2
         000003970824FD05 @   27 : 5d fa f9 06       CallUndefinedReceiver1 r1, r2, [6]
         000003970824FD09 @   31 : 26 fb             Star r0
         000003970824FD0B @   33 : aa                Return
Constant pool (size = 4)
000003970824FCB1: [FixedArray] in OldSpace
 - map: 0x0397080404b1 <Map>
 - length: 4
           0: 0x03970824fc61 <FixedArray[2]>
           1: 0x03970824fc01 <String[#5]: hello>
           2: 0x03970824fbf1 <String[#2]: hi>
           3: 0x0397081c692d <String[#3]: log>
Handler Table (size = 0)
Source Position Table (size = 0)
[generated bytecode for function: log (0x03970824fc71 <SharedFunctionInfo log>)]
Parameter count 2
Register count 2
Frame size 16
         000003970824FE4E @    0 : 13 00 00          LdaGlobal [0], [0]
         000003970824FE51 @    3 : 26 fa             Star r1
         000003970824FE53 @    5 : 28 fa 01 02       LdaNamedProperty r1, [1], [2]
         000003970824FE57 @    9 : 26 fb             Star r0
         000003970824FE59 @   11 : 59 fb fa 02 04    CallProperty1 r0, r1, a0, [4]
         000003970824FE5E @   16 : 0d                LdaUndefined
         000003970824FE5F @   17 : aa                Return
Constant pool (size = 2)
000003970824FE1D: [FixedArray] in OldSpace
 - map: 0x0397080404b1 <Map>
 - length: 2
           0: 0x0397081c68b9 <String[#7]: console>
           1: 0x0397081c692d <String[#3]: log>
Handler Table (size = 0)
Source Position Table (size = 0)
hello

5、测试文件test1.js

let a = {x:1}

function bar(obj) { 
  return obj.x 
}

function foo (count) { 
  let ret = 0
  for(let i = 1; i < count; i++) {
    ret += bar(a)
  }
  return ret
}

//foo(7049)
//foo(100000)

6、输出优化信息

//foo(7049)
d8 --trace-opt-verbose test1.js
[not yet optimizing foo, not enough ticks: 0/2 and ICs changed]

//foo(10000)
[not yet optimizing foo, not enough ticks: 0/2 and ICs changed]
[marking 0x01410824fe35 <JSFunction foo (sfi = 000001410824FCD9)> for optimized recompilation, reason: small function]
[compiling method 0x01410824fe35 <JSFunction foo (sfi = 000001410824FCD9)> using TurboFan OSR]
[optimizing 0x01410824fe35 <JSFunction foo (sfi = 000001410824FCD9)> - took 141.370, 62.753, 1.551 ms]

7、输出反优化信息

//没有输出。。。
d8 --trace-deopt test1.js

8、test2.js

function strToArray(str) {
  let i = 0
  const len = str.length
  let arr = new Uint16Array(str.length)
  for (; i < len; ++i) {
    arr[i] = str.charCodeAt(i)
  }
  return arr;
}

function foo() {
  let i = 0
  let str = 'test V8 GC'
  while (i++ < 1e5) {
    strToArray(str);
  }
}

foo()

9、输出反优化信息

d8 --trace-gc test2.js
[4600:0000019D00000000]      490 ms: Scavenge 1.2 (2.4) -> 0.3 (3.4) MB, 14.5 / 0.0 ms  (average mu = 1.000, current mu = 1.000) allocation failure
[4600:0000019D00000000]      500 ms: Scavenge 1.2 (3.4) -> 0.3 (3.6) MB, 2.0 / 0.0 ms  (average mu = 1.000, current mu = 1.000) allocation failure
[4600:0000019D00000000]      501 ms: Scavenge 1.3 (3.6) -> 0.3 (3.6) MB, 0.1 / 0.0 ms  (average mu = 1.000, current mu = 1.000) allocation failure
[4600:0000019D00000000]      503 ms: Scavenge 1.3 (3.6) -> 0.3 (3.6) MB, 0.1 / 0.0 ms  (average mu = 1.000, current mu = 1.000) allocation failure
[4600:0000019D00000000]      505 ms: Scavenge 1.3 (3.6) -> 0.3 (3.6) MB, 0.1 / 0.0 ms  (average mu = 1.000, current mu = 1.000) allocation failure
[4600:0000019D00000000]      506 ms: Scavenge 1.3 (3.6) -> 0.3 (3.6) MB, 0.1 / 0.0 ms  (average mu = 1.000, current mu = 1.000) allocation failure
[4600:0000019D00000000]      508 ms: Scavenge 1.3 (3.6) -> 0.3 (3.6) MB, 0.1 / 0.0 ms  (average mu = 1.000, current mu = 1.000) allocation failure
[4600:0000019D00000000]      509 ms: Scavenge 1.3 (3.6) -> 0.3 (3.6) MB, 0.1 / 0.0 ms  (average mu = 1.000, current mu = 1.000) allocation failure
[4600:0000019D00000000]      511 ms: Scavenge 1.3 (3.6) -> 0.3 (3.6) MB, 0.1 / 0.0 ms  (average mu = 1.000, current mu = 1.000) allocation failure
[4600:0000019D00000000]      513 ms: Scavenge 1.3 (3.6) -> 0.3 (3.6) MB, 0.1 / 0.0 ms  (average mu = 1.000, current mu = 1.000) allocation failure
[4600:0000019D00000000]      515 ms: Scavenge 1.3 (3.6) -> 0.3 (3.6) MB, 0.1 / 0.0 ms  (average mu = 1.000, current mu = 1.000) allocation failure
[4600:0000019D00000000]      516 ms: Scavenge 1.3 (3.6) -> 0.3 (3.6) MB, 0.1 / 0.0 ms  (average mu = 1.000, current mu = 1.000) allocation failure
[4600:0000019D00000000]      518 ms: Scavenge 1.3 (3.6) -> 0.3 (3.6) MB, 0.1 / 0.0 ms  (average mu = 1.000, current mu = 1.000) allocation failure
[4600:0000019D00000000]      520 ms: Scavenge 1.3 (3.6) -> 0.3 (3.6) MB, 0.1 / 0.0 ms  (average mu = 1.000, current mu = 1.000) allocation failure

10、test3.js

function Foo(property_num,element_num) {
  //添加可索引属性
  for (let i = 0; i < element_num; i++) {
      this[i] = `element${i}`
  }
  //添加常规属性
  for (let i = 0; i < property_num; i++) {
      let ppt = `property${i}`
      this[ppt] = ppt
  }
}
var bar = new Foo(10,10)
console.log(%HasFastProperties(bar));
delete bar.property2
console.log(%HasFastProperties(bar));

11、使用内部方法

//%HasFastProperties测试是否有快属性
d8 --allow-natives-syntax test3.js
true
false

编译V8引擎

1、安装Visual Studio 2017

2、从微软下载Windows 10 SDK,安装“Debugging Tools for Windows”

https://developer.microsoft.com/en-US/windows/downloads/windows-10-sdk/

3、设置代理

# git的http代理设置
git config --global http.proxy 127.0.0.1:9528
git config --global https.proxy 127.0.0.1:9528

# 可以通过操作系统设置http代理
# 也可以通过通过命令行设置http代理设置(管理员权限)
netsh winhttp set proxy 127.0.0.1:9528

# 设置cipd_client的http代理设置
set HTTP_PROXY=127.0.0.1:9528
set HTTPS_PROXY=127.0.0.1:9528

4、环境变量

# 启动 VS2017 x86_amd64 命令行环境
"C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\VC\Auxiliary\Build\vcvarsx86_amd64.bat"

# 环境变量
set PATH=D:\GitGoogleV8\depot_tools_win;D:\NeoLang\Python\Python37;%PATH%
set DEPOT_TOOLS_WIN_TOOLCHAIN=0

5、下载源码

mkdir V8Build
cd V8Build
# V8Build
fetch v8

cd v8
# V8Build/v8
git pull origin 8.3.82
cd ..

6、同步环境

# V8Build
gclient sync

7、生成解决方案

cd v8/src
# V8Build/v8/src
gn gen --ide=vs out\Default
# 也可以设置filters,如果是看chrome源码,最好设置一下,因为有几千个项目
# gn gen --ide=vs --filters=//chrome --no-deps out\Default

8、编译

# 用vs打开sln,并编译
# 编译过程中,有时会出现文件无法访问等情况
# 可以在命令行中执行,然后用VS继续编译就可以成功了
# V8Build/v8/src
devenv out\Default\all.sln

9、测试

# V8Build/v8/src
cd out\Default
# V8Build/v8/src/out/Default
v8_shell.exe

JSP输出容器的JVM参数

<%@ page language="java" import="java.util.*, java.lang.management.*" pageEncoding="UTF-8"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>系统参数输出</title>
<style type="text/css">
table
{
    border-collapse:collapse;
}
td
{
    border:1px solid #427BD6;
}
</style>
</head>

<body style="background-color:#f2f2f2;">

<h1>环境变量</h1>
<table>
<%
    Map<String, String> map = System.getenv();
    //RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean();
    //Map<String, String> map = runtimeMxBean.getSystemProperties();
    for(String key : map.keySet())
    {
        out.println("<tr>");
        out.println("<td>");
        out.println(key);
        out.println("</td>");
        out.println("<td>");
        out.println(map.get(key));
        out.println("</td>");
        out.println("</tr>");
    }     
%>
</table>

<h1>JVM参数</h1>
<table>
<% 
    RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean();
    
    out.println("<tr>");
    out.println("<td>");
    out.println(runtimeMxBean.getVmVendor()+" || "+runtimeMxBean.getVmName() +" || "+ runtimeMxBean.getVmVersion());
    out.println("</td>");
    out.println("</tr>");
    
    out.println("<tr>");
    out.println("<td>");
    out.println(runtimeMxBean.getSpecVendor()+" || "+runtimeMxBean.getSpecName() +" || "+ runtimeMxBean.getSpecVersion());
    out.println("</td>");
    out.println("</tr>");
    
    out.println("<tr>");
    out.println("<td>");
    out.println(runtimeMxBean.getName()+" || "+runtimeMxBean.getStartTime() +" || "+ runtimeMxBean.getUptime());
    out.println("</td>");
    out.println("</tr>");
    
    out.println("<tr>");
    out.println("<td>");
    out.println("MaxMemory="+Runtime.getRuntime().maxMemory()/1024/1024 + "m ");
    out.println("TotalMemory="+Runtime.getRuntime().totalMemory()/1024/1024 + "m ");
    out.println("FreeMemory="+Runtime.getRuntime().freeMemory()/1024/1024 + "m ");
    out.println("</td>");
    out.println("</tr>");
    
    List<MemoryPoolMXBean> memoryPoolMXBeans = ManagementFactory.getMemoryPoolMXBeans();
    for (MemoryPoolMXBean bean : memoryPoolMXBeans)
    {
        out.println("<tr>");
        out.println("<td>");
        out.println(bean.getName()+" || "+bean.getType()+" || Init="+bean.getUsage().getInit()/1024/1024+
        "m Max="+bean.getUsage().getMax()/1024/1024+"m Used="+bean.getUsage().getUsed()/1024/1024+
        "m Committed="+bean.getUsage().getCommitted()/1024/1024+"m ");
        out.println("</td>");
        out.println("</tr>");
    }
    
    List<String> arguments = runtimeMxBean.getInputArguments();
    for(String arg:arguments)
    {
        out.println("<tr>");
        out.println("<td>");
        out.println(arg);
        out.println("</td>");
        out.println("</tr>");
    }
%>
</table>

<h1>系统参数</h1>
<table>
<%
    Properties props = System.getProperties();
    for(Object o:props.keySet())
    {
        out.println("<tr>");
        out.println("<td>");
        out.println(o);
        out.println("</td>");
        out.println("<td>");
        out.println(props.get(o));
        out.println("</td>");
        out.println("</tr>");
    }
%>
</table>
</body>
</html>

Java四种引用方式

最近写了个例子,说明了一下强引用、软引用、弱引用、虚引用的区别。

1、NString.java

package com.neohope.reference;

/**
 * Created by Hansen
 */
public class NString {
    private String name;
    private String value;

    public NString(String name, String value)
    {
        this.name = name;
        this.value=value;
    }

    public NString(String name, StringBuilder builder)
    {
        this.name = name;
        this.value=builder.toString();
    }

    public String Name()
    {
        return name;
    }

    public String Value()
    {
        return value;
    }

    @Override
    protected void finalize()
    {
        System.out.println(">> "+Name()+" finalize called");
        try {
            super.finalize();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
    }
}

2、TestReferenceA.java

package com.neohope.reference;

import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.util.WeakHashMap;

/**
 * Created by Hansen
 * JVM参数:
 * -XX:+PrintGCDetails
 * -Xms6m -Xmx12m
 */
public class TestReferenceA {
    public static void main(String[] args) {
        //申请内存
        Runtime rt=Runtime.getRuntime();
        System.out.println("Total memory is "+rt.totalMemory());
        int MemSize = 1024*1024*1;
        StringBuilder builder = new StringBuilder(MemSize);
        for (int i=0;i<MemSize/8;i++) {
            builder.append("ABCDEFGH");
        }
        System.out.println("Total memory is "+rt.totalMemory());

        //用s00与s01两个大字符串演示强引用与软引用的区别
        NString s00 = new NString("s00",builder);
        NString s01 = new NString("s01",builder);
        //s02与s03演示WeakReference的两种用法
        NString s02 = new NString("s02","s03");
        String key03 = new String("k03");
        NString s03 = new NString("s03","s03");
        //s04演示PhantomReference
        NString s04 = new NString("s04","s04");
        System.out.println("Total memory is "+rt.totalMemory());

        //第1类、强引用的对象,也最常用到的类型,内存不足时JVM将抛出异常也不要释放这些内存
        NString strongRef = s00;
        //第2类、只有软引用的数据,内存不足时JVM会回收这些内存
        SoftReference<NString> softRef = new SoftReference<NString>(s01);
        //第3类、只有弱引用的数据,无论内存是否充足JVM会回收这些内存,也就是弱引用不会影响对象的生命周期
        WeakReference<NString> weakRef = new WeakReference<NString>(s02);
        //WeakHashMap很有用,当key022为null时,s022也就会被GC咯
        WeakHashMap weakHashMap = new WeakHashMap();
        weakHashMap.put(key03,s03);
        //第4类、只有虚引用的数据,一开始refQueue为空,被GC才会有值
        ReferenceQueue refQueue = new ReferenceQueue<String>();
        PhantomReference<NString> phantomRef = new PhantomReference<NString>(s04, refQueue);

        //有强引用时,输出初始化情况
        System.out.println("With strong ref strongRef is " + (strongRef==null?"null":"not null"));
        System.out.println("With strong ref softRef is " + (softRef.get()==null?"null":"not null"));
        System.out.println("With strong ref weakRef is " + (weakRef.get()==null?"null":"not null"));
        System.out.println("With strong ref weakHashMap is " + (weakHashMap.isEmpty() ?"empty":"not empty"));
        //这里为null,当对象被GC时,会得到虚引用对象
        System.out.println("With strong ref refQueue has " + refQueue.poll());
        System.out.println();

        //去掉初始的强引用
        s00 = null;
        s01 = null;
        s02 = null;
        s03 = null;
        s04 = null;

        //多数情况下,没有来得及GC
        System.out.println("Before gc strongRef is " + (strongRef==null?"null":"not null"));
        System.out.println("Before gc softRef is " + (softRef.get()==null?"null":"not null"));
        System.out.println("Before gc weakRef is " + (weakRef.get()==null?"null":"not null"));
        System.out.println("Before gc weakHashMap is " + (weakHashMap.isEmpty() ?"empty":"not empty"));
        //null,当对象被GC时,会得到虚引用对象
        System.out.println("Before gc refQueue has " + refQueue.poll());
        System.out.println();

        //强制GC
        System.gc();
        System.out.println("After first gc strongRef is " + (strongRef==null?"null":"not null"));
        System.out.println("After first gc softRef is " + (softRef.get()==null?"null":"not null"));
        //null,weakRef已经被释放了
        System.out.println("After first gc weakRef is " + (weakRef.get()==null?"null":"not null"));
        System.out.println("After first gc weakHashMap is " + (weakHashMap.isEmpty() ?"empty":"not empty"));
        //与运行环境及GC方法有关,会出现一次有值的情况,然后一直为null
        System.out.println("After first gc refQueue has " + refQueue.poll());
        System.out.println();

        //将key03设为null,强制GC
        key03 = null;
        System.gc();
        System.out.println("After second gc strongRef is " + (strongRef==null?"null":"not null"));
        System.out.println("After second gc softRef is " + (softRef.get()==null?"null":"not null"));
        //null
        System.out.println("After second gc weakRef is " + (weakRef.get()==null?"null":"not null"));
        //empty,由于key被设为空,则会释放掉弱引用对象了
        System.out.println("After second gc weakHashMap is " + (weakHashMap.isEmpty() ?"empty":"not empty"));
        //与运行环境及GC方法有关,会出现一次有值的情况,然后一直为null
        System.out.println("After second gc refQueue has " + refQueue.poll());
        System.out.println();

        //Finalization
        System.runFinalization();
        System.out.println("After Finalization strongRef is " + (strongRef==null?"null":"not null"));
        System.out.println("After Finalization softRef is " + (softRef.get()==null?"null":"not null"));
        //null
        System.out.println("After Finalization weakRef is " + (weakRef.get()==null?"null":"not null"));
        //empty
        System.out.println("After Finalization weakHashMap is " + (weakHashMap.isEmpty() ?"empty":"not empty"));
        //与运行环境及GC方法有关,会出现一次有值的情况,然后一直为null
        System.out.println("After Finalization refQueue has " + refQueue.poll());
        System.out.println();

        //申请内存,造成内存不足
        System.out.println("Total memory is "+rt.totalMemory());
        StringBuilder builder2 = new StringBuilder(MemSize);
        for (int i=0;i<MemSize/8;i++) {
            builder2.append("ABCDEFGH");
        }
        NString tmp0 = new NString("tmp0", builder2);
        System.out.println("Total memory is "+rt.totalMemory());

        System.out.println("When mem is low strongRef is " + (strongRef==null?"null":"not null"));
        //null,内存不足时软引用会被释放
        System.out.println("When mem is low softRef is " + (softRef.get()==null?"null":"not null"));
        //null
        System.out.println("When mem is low weakRef is " + (weakRef.get()==null?"null":"not null"));
        //empty
        System.out.println("When mem is low weakHashMap is " + (weakHashMap.isEmpty() ?"empty":"not empty"));
        //与运行环境及GC方法有关,会出现一次有值的情况,然后一直为null
        System.out.println("When mem is low refQueue has " + refQueue.poll());
        System.out.println();

        //再次申请内存,JMV会内存爆掉
        NString tmp1 = null;
        try
        {
            tmp1 = new NString("tmp1",builder2);
        }
        catch(OutOfMemoryError ex)
        {
            System.out.println("Not enough memory");
        }

        //null
        System.out.println("tmp1 is " + (tmp1==null?"null":"not null"));
        //就算是抛出OutOfMemoryError,仍然不会释放
        System.out.println("When not enough memory strongRef is " + (strongRef==null?"null":"not null"));
        //null
        System.out.println("When not enough memory softRef is " + (softRef.get()==null?"null":"not null"));
        //null
        System.out.println("When not enough memory weakRef is " + (weakRef.get()==null?"null":"not null"));
        //empty
        System.out.println("When not enough memory weakHashMap is " + (weakHashMap.isEmpty() ?"empty":"not empty"));
        //null
        System.out.println("When not enough memory refQueue has " + refQueue.poll());
        System.out.println();
    }
}

3、输出

Total memory is 6094848
Total memory is 6094848
Total memory is 11390976
With strong ref strongRef is not null
With strong ref softRef is not null
With strong ref weakRef is not null
With strong ref weakHashMap is not empty
With strong ref refQueue has null

Before gc strongRef is not null
Before gc softRef is not null
Before gc weakRef is not null
Before gc weakHashMap is not empty
Before gc refQueue has null

After first gc strongRef is not null
After first gc softRef is not null
After first gc weakRef is null
After first gc weakHashMap is not empty
>> s04 finalize called
After first gc refQueue has null
>> s02 finalize called

After second gc strongRef is not null
After second gc softRef is not null
After second gc weakRef is null
After second gc weakHashMap is not empty
After second gc refQueue has java.lang.ref.PhantomReference@15d63da

After Finalization strongRef is not null
After Finalization softRef is not null
After Finalization weakRef is null
After Finalization weakHashMap is not empty
After Finalization refQueue has null

Total memory is 14221312
Total memory is 14221312
When mem is low strongRef is not null
When mem is low softRef is not null
When mem is low weakRef is null
When mem is low weakHashMap is not empty
When mem is low refQueue has null

>> s01 finalize called
Not enough memory
tmp1 is null
When not enough memory strongRef is not null
When not enough memory softRef is null
When not enough memory weakRef is null
When not enough memory weakHashMap is empty
When not enough memory refQueue has null

Java程序如何加密

今天在想Java程序加密的事情,现在市面上的方法,无非是混淆代码,jar包签名等,瞬间就能被破解掉。

我想到了一个很挫的方法,和PE文件加壳脱壳一样,class文件/jar文件为什么不可以呢?

但这样做的话,是有限制的,就是客户必须使用你自己定制的JVM及容器,否则是无法运行的。

具体方法如下:
1、生成class文件后,按一定规则进行加密处理。偷懒的话,直接对称加密好了。
2、生成jar包的时候,同样按一定规则进行加密处理。偷懒的话,zip的时候,增加一个强壮的密码就好了。
3、下载并编译OpenJDK,在读取jar包内容,和class文件的地方,要进行脱壳处理。按上面的思路,就是解压缩和解密处理。
4、当然,定制部分的dll和exe,需要进行PE加壳处理

当然,上面这种方式的话,和加壳脱壳还是有很大的区别的,就是不能自行解压,并要依赖于JVM甚至容器的定制。

如果要真正实现自解压处理的话,就要多做几步:

具体方法如下:
1、编译生成class文件,按一定规则进行加密处理。偷懒的话,直接对称加密好了。
2、生成jar包的时候,同样按一定规则进行加密处理。偷懒的话,zip的时候,增加一个强壮的密码就好了。
3、将需要的所有jar包,按你喜欢的方法,生成一个jar包列表,并打成一个巨大的资源文件。
4、写一个java引导文件,用于处理运行参数,比如入口程序等。
5、写一个自定义classloader+jni+dll,用于读取巨大的资源文件中的jar包及class文件
6、用普通的jvm启动程序,初始化时用引导文件+自定义classloader
7、引导文件+自定义classloader将需要的文件直接解压到内存中,提供给jvm使用
8、dll部分要进行PE加壳处理

JVM常用参数设置

运行方式设置
-client
-server
两者的区别在JIT的编译比例及GC的方式不同,也可以近似的这样理解:client会启动比较快,GC默认为串行,但JIT编译较少,速度慢一些;server启动慢,GC默认为并行,但JIT编译较多,开始速度慢,但JIT编译后会越来越快。

内存设置
-Xms
设置初始化堆的大小

-Xmx
设置最大堆大小

-Xss 或 -XX:ThreadStackSize
设置每个线程栈大小

-Xmn 或 -XX:NewSize
设置年轻代的内存大小

-XX:MaxNewSize
设置最大年轻代新生代内存大小

-XX:PermSize
设置持久代内存大小

-XX:MaxPermSize
设置最大值持久代内存大小

-XX:NewRatio
设置年轻代与年老代的比值

-XX:SurvivorRatio
设置年轻代中Eden区与Survivor区的大小比值

GC设置
-XX:+DisableExplicitGC
禁用java代码中的System.gc()

-XX:+UseSerialGC
设置串行收集器

-XX:+UseParallelGC 或 -XX:+UseParNewGC
设置年轻代为并行收集器

-XX:ParallelGCThreads
设置并行收集器的线程数

或 -XX:+UseParallelOldGC
设置年老代为并行收集

-XX:+UseConcMarkSweepGC
设置年老代为CMS收集

-XX:MaxGCPauseMillis
一次GC最大的停顿时间

-XX:GCTimeRatio
GC占用CPU时间比例

调试GC
-verbose:gc
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-XX:+PrintGCApplicationConcurrentTime
-XX:+PrintGCApplicationStoppedTime
-XX:PrintHeapAtGC

调试加载类
-verbose:class 监视加载的类的情况

调试JNI
-verbose:jni

设置字符集为UTF-8
-Djavax.servlet.request.encoding=utf-8
-Dfile.encoding=utf-8

调试SSL
-Djavax.net.debug=ssl:record
-Djavax.net.debug=ssl:handshake
-Djavax.net.debug=all

内存溢出时抓取JVM快照
-XX:+HeapDumpOnOutOfMemoryError