JVM字节码与执行引擎
JVM 内存布局是排查线上问题的基础。很多开发者遇到 OutOfMemoryError 时不知道从哪里入手。本文结合实际案例,讲清楚各区域的作用和常见异常,帮你建立排查思路。
字节码概览
Java 字节码是 JVM 的指令集,每条指令由一个字节的操作码(Opcode)和零到多个操作数组成。
#
查看字节码
# javap 反编译 |
#
HelloWorld 字节码示例
public class HelloWorld { |
public static void main(java.lang.String[]); |
常见字节码指令
#
加载和存储
| 指令 | 说明 |
|---|---|
| iload | 将 int 局部变量压入操作数栈 |
| istore | 将栈顶 int 存入局部变量 |
| ldc | 将常量压入操作数栈 |
| getstatic | 获取静态字段 |
| putfield | 设置对象字段 |
#
算术运算
| 指令 | 说明 |
|---|---|
| iadd | int 加法 |
| isub | int 减法 |
| imul | int 乘法 |
| idiv | int 除法 |
| iinc | 局部变量自增 |
#
类型转换
| 指令 | 说明 |
|---|---|
| i2l | int 转 long |
| i2f | int 转 float |
| l2i | long 转 int |
| checkcast | 类型检查转换 |
#
对象操作
| 指令 | 说明 |
|---|---|
| new | 创建对象 |
| invokespecial | 调用构造方法/私有方法/父类方法 |
| invokevirtual | 调用虚方法 |
| invokestatic | 调用静态方法 |
| invokeinterface | 调用接口方法 |
| invokedynamic | 动态调用(Java 7+ Lambda) |
#
控制转移
| 指令 | 说明 |
|---|---|
| ifeq/ifne | 等于/不等于0跳转 |
| if_icmplt | int 比较小于跳转 |
| goto | 无条件跳转 |
| tableswitch | 表格开关 |
| lookupswitch | 查找开关 |
执行引擎
#
1. 解释执行(Interpreter)
字节码 |
特点:启动快,执行慢。
#
2. JIT 编译(Just-In-Time)
字节码 |
热点检测:
-XX:CompileThreshold=10000 # 方法调用次数阈值(Client: 1500, Server: 10000) |
#
3. 分层编译(Tiered Compilation)
JDK 7+ 默认开启:
Level 0: 解释执行 |
-XX:+TieredCompilation # 开启分层编译(默认开启) |
C1 vs C2 编译器
| 特性 | C1(Client Compiler) | C2(Server Compiler) |
|---|---|---|
| 优化程度 | 简单 | 激进 |
| 编译速度 | 快 | 慢 |
| 生成代码质量 | 一般 | 高 |
| 启动速度 | 快 | 慢 |
| 适用场景 | 客户端/短运行 | 服务端/长运行 |
JIT 优化技术
#
1. 方法内联
public int add(int a, int b) { |
-XX:MaxInlineSize=35 # 方法体小于35字节内联 |
#
2. 逃逸分析
public void method() { |
-XX:+DoEscapeAnalysis # 开启逃逸分析 |
#
3. 锁消除
public void method() { |
#
4. 循环展开
// 原始代码 |
查看 JIT 编译
# 打印 JIT 编译日志 |
# 打印更详细的信息 |
AOT 编译(Ahead-Of-Time)
JDK 9+ 引入 jaotc:
# 编译为本地代码 |
特点:
- 启动即 peak performance
- 减少 JIT 编译开销
- 但失去跨平台性
总结
| 执行方式 | 启动速度 | 峰值性能 | 适用场景 |
|---|---|---|---|
| 解释执行 | 最快 | 最慢 | 极少 |
| C1 编译 | 快 | 中 | 客户端 |
| C2 编译 | 慢 | 最高 | 服务端 |
| 分层编译 | 较快 | 最高 | 现代 JVM 默认 |
| AOT | 最快 | 高 | 容器/Serverless |
JVM 的执行引擎设计精妙:解释执行快速启动,JIT 编译提升峰值性能,分层编译兼顾两者,AOT 为云原生场景提供新选择。
核心要点
堆内存分为年轻代和老年代,年轻代又分为 Eden、Survivor 等区域
栈内存是线程私有的,每个线程都有自己的栈空间
方法区(元空间)存储类信息、常量池等
常见的 OOM 类型:HeapSpace、OutOfMemoryError、StackOverflowError
总结
理解 JVM 内存模型是成为高级 Java 工程师的必备技能。在实际项目中,配置合适的堆内存大小、选择合适的垃圾收集器,都需要对内存布局有清晰的认识。