Java 运行原理(超级详细版)——从你写下一行代码到程序真正执行的全过程
这是 Java 基础中最核心、最底层、最常被问到的知识点之一。
无论你是准备面试、考研、蓝桥杯,还是真正想理解 Java 为什么“一次编写,到处运行”,都强烈建议把这一块吃透。
一、Java 程序从写出来到运行起来的完整流程(最清晰版)
你写的代码 → 编译阶段 → 运行阶段
HelloWorld.java →→→ javac 编译 →→→ HelloWorld.class
↓
java 命令启动
↓
JVM(Java虚拟机)启动
↓
1. 类加载子系统(Class Loader)
2. 运行时数据区(内存划分)
3. 执行引擎(解释器 + JIT编译器)
4. 本地方法接口(JNI)
↓
程序真正执行并输出结果
一句话总结:
Java 是先编译后解释的混合执行语言,核心靠 JVM(Java Virtual Machine)实现跨平台。
二、详细拆解每一步(带图文流程)
1. 编写阶段:.java 源文件
// HelloWorld.java
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, Java运行原理!");
}
}
- 这是一个纯文本文件,人类可读。
- 后缀必须是
.java - 文件名必须和 public 类名 完全一致(区分大小写)
2. 编译阶段:javac 把 .java → .class
命令:
javac HelloWorld.java
发生了什么?
- 词法分析 → 把代码切成一个个 token(关键字、标识符、运算符、数字、字符串等)
- 语法分析 → 构建语法树,检查语法是否正确(比如大括号是否匹配)
- 语义分析 → 检查类型是否匹配、变量是否声明、方法是否被正确调用等
- 生成字节码 → 把语法树转成 JVM 能理解的平台无关的字节码(.class 文件)
生成的 .class 文件长什么样?(用 javap 反编译看一下)
javap -c HelloWorld
你会看到类似下面的字节码(这是 JVM 真正能读懂的东西):
public static void main(java.lang.String[]);
Code:
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #7 // String Hello, Java运行原理!
5: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
重要结论:
- .class 文件不是机器码,是中间码(字节码)
- 字节码与平台无关(Windows、Mac、Linux 都能用同一份 .class)
- 真正和硬件打交道的是 JVM,不是 .class
3. 运行阶段:java 命令启动 JVM
命令:
java HelloWorld
# 或(推荐写全类名)
java com.example.HelloWorld
这一步真正发生了什么?
JVM 启动后,会经历以下核心流程:
启动 java.exe / java(Windows/Linux/Mac不同) → 加载 JVM 动态库 → 初始化 JVM → 执行 main 方法
JVM 内部核心工作分为三大块:
- 类加载子系统(Class Loader Subsystem)
- 运行时数据区(Runtime Data Area)
- 执行引擎(Execution Engine)
三、JVM 内部最详细的运行原理(重点!)
1. 类加载子系统(Class Loading)
把 .class 文件加载进内存,一共经历这几个阶段:
| 阶段 | 做什么 | 是否必须 | 常见问题点 |
|---|---|---|---|
| 加载(Loading) | 通过类加载器把 .class 文件读进内存 | 必须 | — |
| 链接(Linking) | 验证 + 准备 + 解析 | 必须 | 验证失败会报 VerifyError |
| 验证(Verification) | 校验字节码格式、安全性、指令合法性 | — | — |
| 准备(Preparation) | 为静态变量分配内存,赋默认值(0/null) | — | 注意:不是赋初始值 |
| 解析(Resolution) | 把符号引用转为直接引用(方法、字段、类) | 可选 | — |
| 初始化(Initialization) | 执行静态代码块和静态变量的显式赋值 | 必须 | 最重要的阶段! |
类初始化的触发时机(非常高频面试题):
- new 一个对象
- 访问静态变量 / 静态方法
- 初始化子类(会先初始化父类)
- main 方法所在类
- 使用反射
- …
2. 运行时数据区(内存划分)——最重要的一张图!
┌───────────────────────────────────────────────┐
│ 线程共享区 │
│ ┌───────────────────────────────┐ │
│ │ 方法区(Method Area) │ │
│ │ (1.8+ 叫 元空间 Metaspace) │ │
│ │ 存放:类信息、常量、静态变量 │ │
│ └───────────────────────────────┘ │
│ │
│ ┌───────────────────────────────┐ │
│ │ 堆(Heap) │ │
│ │ 存放:几乎所有对象实例 │ │
│ └───────────────────────────────┘ │
└───────────────────────────────────────────────┘
┌───────────────────────────────────────────────┐
│ 线程私有区 │
│ ┌───────────────────────────────┐ │
│ │ 虚拟机栈(JVM Stack) │ │
│ │ 存放:栈帧(局部变量表、操作数栈)│ │
│ └───────────────────────────────┘ │
│ │
│ ┌───────────────────────────────┐ │
│ │ 本地方法栈(Native Method Stack)│ │
│ └───────────────────────────────┘ │
│ │
│ ┌───────────────────────────────┐ │
│ │ 程序计数器(PC Register) │ │
│ │ 记录当前线程执行的字节码行号 │ │
│ └───────────────────────────────┘ │
└───────────────────────────────────────────────┘
最常考的几个区域对比:
| 区域 | 共享性 | 存放内容 | 可能抛出异常 | 是否会GC |
|---|---|---|---|---|
| 程序计数器 | 线程私有 | 字节码指令地址 | 无(唯一不出错的区域) | 否 |
| 虚拟机栈 | 线程私有 | 栈帧(局部变量、操作数栈等) | StackOverflowError | 否 |
| 本地方法栈 | 线程私有 | native 方法的栈 | StackOverflowError | 否 |
| 堆 | 线程共享 | 对象实例、数组 | OutOfMemoryError | 是 |
| 方法区(元空间) | 线程共享 | 类信息、常量、静态变量、JIT代码 | OutOfMemoryError | 是(部分) |
3. 执行引擎(最核心的执行部分)
JVM 执行字节码有两种方式:
- 解释执行(Interpreter)
- 一条一条解释字节码 → 翻译成机器码 → 执行
- 启动快,但运行慢
- 即时编译(JIT Compiler) — HotSpot 的核心性能保障
- 把热点代码(经常执行的代码)编译成本地机器码
- 编译后直接执行机器码,速度非常快
- 热点判定依据:调用次数 / 循环回边次数达到阈值(-XX:CompileThreshold)
现代 JVM 真实执行方式:
第一次执行 → 解释器解释执行 + 收集 profiling 信息
多次执行 → 达到阈值 → JIT 编译成机器码 → 放入 Code Cache
下次执行 → 直接执行机器码(极快)
四、总结:一句话版运行原理
你写的 .java 文件经过 javac 编译成平台无关的 .class 字节码,然后由 JVM 加载、链接、初始化,在运行时数据区分配内存,由解释器 + JIT编译器混合执行,最终把字节码转成当前机器能直接运行的本地机器码,从而实现“一次编写,到处运行”。
五、面试/考研常考 10 个追问问题(建议全部背熟)
- Java 为什么能跨平台?
- .class 文件是什么?能直接运行吗?
- JVM、JRE、JDK 分别是什么关系?
- 类加载的几个阶段分别做什么?
- 什么时候会触发类的初始化?
- 堆和栈的区别?分别存什么?
- 方法区(元空间)存什么?为什么 1.8 后改成了元空间?
- 解释执行和编译执行的区别?JVM 怎么结合使用的?
- 程序计数器有什么用?为什么线程私有?
- 运行一个 main 方法,JVM 都做了哪些事?
需要我继续深入讲解其中任意一个点吗?
比如:
- 类加载的双亲委派模型
- 字节码具体指令含义
- JIT 编译的热点探测细节
- 内存区域更详细的 GC 相关知识
随时告诉我,我们继续往下挖~