Skip to content

Java 程序运行机制

写完代码点运行,程序就跑起来了。但 javac 编译了什么?JVM 又是怎么执行代码的?理解运行机制,才能理解 Java 跨平台、GC、JIT 等核心特性的原理。

整体流程

源代码(.java)
    ↓ javac 编译
字节码(.class)
    ↓ java 运行
JVM 执行

本地机器码

字节码:平台无关的中间语言

Java 源代码经过 javac 编译后,生成 .class 文件,这就是字节码:

java
// 源代码
public class Hello {
    public static void main(String[] args) {
        System.out.println("Hello");
    }
}

编译后生成 Hello.class,用十六进制查看(部分):

CA FE BA BE 00 00 00 37 00 0D 0A 00 05 00 0C 07
00 0D 07 00 0E 0C 00 09 00 03 00 0C 01 00 06 3C
69 6E 69 74 3E 01 00 03 28 29 56 ...

字节码是一种中间代码,不是机器码。它由 JVM 执行,而不是 CPU 直接执行。关键点:字节码是统一的,JVM 是多样的——不同平台有各自的 JVM 实现,但字节码格式完全一致。

javac 编译时做了四件事:语法检查、语义检查、符号解析、生成字节码。

JVM 架构

┌─────────────────────────────────────────────────────────────┐
│                         JVM                                 │
├─────────────────────────────────────────────────────────────┤
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐         │
│  │  类加载器   │  │  执行引擎   │  │   本地接口  │         │
│  │ ClassLoader │  │  Interpreter│  │  JNI/Native│         │
│  └──────┬──────┘  │    JIT      │  └─────────────┘         │
│         │         └──────┬──────┘                          │
│  ┌──────▼────────────────▼──────────────────────────┐     │
│  │                   运行时数据区                        │     │
│  │  ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐  │     │
│  │  │ 方法区   │ │   堆    │ │  栈    │ │  PC    │  │     │
│  │  │MethodArea│ │  Heap  │ │  Stack │ │Register │  │     │
│  │  └─────────┘ └─────────┘ └─────────┘ └─────────┘  │     │
│  └──────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────┘

类加载器

三层结构:Bootstrap ClassLoader(核心类库)→ Platform ClassLoader(扩展类)→ Application ClassLoader(应用程序类)。双亲委派模型保证了类的安全性——你写的 java.lang.String 不会覆盖核心类库。

运行时数据区

区域用途
堆(Heap)对象实例、数组,GC 主要管理区域
栈(Stack)方法调用栈帧、局部变量
方法区(Method Area)类信息、常量池、静态变量
程序计数器(PC)当前执行字节码行号

执行引擎

解释器逐行执行字节码,JIT 编译器将热点代码编译为本地机器码,GC 自动回收不再使用的对象。

JIT 编译

现代 JVM 采用分层编译,平衡启动速度和运行效率:

层次说明特点
Tier 0解释执行启动快
Tier 1C1 编译快速编译,优化一般
Tier 2C2 编译深度优化,编译较慢

JVM 通过热点检测识别热点代码:方法调用计数器统计方法调用次数,回边计数器统计循环体执行次数。超过阈值(如 10000 次)的代码会被 JIT 编译为机器码。

这带来一个有趣的现象:Java 程序运行得越久,往往越快

为什么 Java 能跨平台

三个关键点:字节码统一、JVM 适配各平台、接口一致。

同一份 Hello.class

    ├── Windows JVM → Windows 机器码
    ├── Linux JVM → Linux 机器码
    └── macOS JVM → macOS 机器码

后续可阅读:JVM/JRE/JDK 区别编译型 vs 解释型垃圾回收原理

基于 VitePress 构建