Java代码运行过程
1) 从 .java 到 .class:编译成字节码
① 你写的是 Java 源码
比如:
1 | public class Hello { |
② javac 编译器把源码编译成字节码(Bytecode)
执行:
1 | javac Hello.java |
会得到:
Hello.class
.class 里不是某个 CPU(x86/ARM)的机器指令,而是一种 平台无关 的指令集:JVM 字节码。
这就是“一次编译,到处运行”的核心原因:
不同操作系统/CPU 上只要有对应的 JVM 实现,就能执行同一份.class字节码。
2) 从启动到执行:JVM 进程、类加载与初始化
运行时:
1 | java Hello |
发生的事大致是:
① 启动 JVM 进程
java 命令会启动一个进程,创建 JVM(例如 HotSpot JVM),准备运行环境。
② 找到并加载主类(Hello)
JVM 要执行 main,必须先把 Hello.class 装进来。这个过程由 **类加载器(ClassLoader)**完成。
③ 类加载的三个关键阶段
通常分为(概念上):
Loading(加载):把
.class的二进制数据读入内存,生成Class对象Linking(链接):让类“可用”
Verification(验证):检查字节码是否合法、安全(类型、栈、跳转等)
Preparation(准备):给
static变量分配内存并设默认值Resolution(解析):把常量池里符号引用(类名/方法名)变成直接引用(指针/句柄)
Initialization(初始化):执行类的
<clinit>,也就是静态变量的显式赋值 +static {}块
④ 类加载器的“父类委派”思想
常见的类加载器体系(不同 JVM 名称略有差异):
Bootstrap:加载核心类库(如
java.lang.*)Platform/Extension:平台扩展类库
Application:应用 classpath 下的类
以及你自定义的 ClassLoader
典型机制:先问父加载器能不能加载,父不行我再加载。这样可以避免核心类被随意替换,提高安全性与一致性。
3) 真正开始跑:解释执行 + JIT 编译(热点编译)
当类加载好,JVM 开始执行 main 方法里的字节码。执行引擎通常包含两条路:
① 解释器(Interpreter)
一条条读取字节码指令,逐条执行
优点:启动快
缺点:长期跑起来速度不如纯机器码
② JIT 编译器(Just-In-Time)
JVM 会在运行中“观察”哪些代码被频繁执行(热点),然后把这些热点字节码 编译为本机机器码,并做大量优化(内联、逃逸分析、去虚拟化、循环优化等)。
于是整体过程常常是:
先解释执行(快启动)
运行一段时间后,热点代码被 JIT 编译(性能越来越高)
这也是为什么很多 Java 程序会有“预热(warm-up)”现象:刚启动时慢一些,跑一会更快。
(很多现代 JVM 还有“分层编译”思路:先用轻量编译快速提升,再用更激进的优化提升峰值性能。)
4) JVM 的内存区域:代码、对象、线程栈分别放哪?
为了让字节码能执行,JVM 运行时会划分一些典型内存区域(概念模型,各 JVM 实现细节会不同):
① 堆(Heap)
存放对象实例(
new出来的对象)通常是 GC 重点管理区域
大多数情况下占用最大
② 虚拟机栈(Java Stack)
每个线程独有
每次方法调用会创建一个 栈帧(Stack Frame)
栈帧里放:局部变量表、操作数栈、返回地址、部分方法调用信息
方法返回后栈帧出栈
StackOverflowError常见原因:递归过深/栈帧太多。
③ 方法区 / 元空间(Method Area / Metaspace)
存类的元数据:类信息、方法信息、字段信息、运行时常量池等
现代 HotSpot 常用 **Metaspace(元空间)**作为实现之一(具体实现细节会随版本变化)
④ 程序计数器(PC Register)
- 每个线程一个,用来记录当前执行到哪条字节码指令(线程切换后能继续)
⑤ 本地方法栈(Native Method Stack)
- 为 JNI / 本地方法服务(调用 C/C++ 等)
5) 垃圾回收(GC):对象怎么被自动释放?
Java 里对象通常不靠你手动 free,而是 JVM 的 **垃圾回收器(GC)**自动回收堆内存。
① 核心思路:可达性分析(Reachability)
从一组 GC Roots 出发(如线程栈中的引用、静态引用等),能一路引用到的对象认为“活着”;不可达的对象就可以回收。
② 分代思想(常见但非绝对)
很多 GC 会把对象按“存活时间/特征”分区域管理:
新创建对象多、死亡快:回收频繁但成本低
存活久的对象:回收频率低但每次更重
③ 停顿与并发
GC 有时会引入 Stop-The-World 停顿;现代 GC 也大量使用并发/并行技术来降低停顿时间。
6) 线程与同步:为什么 Java 多线程能工作?
JVM 会把 Java 线程映射到底层 OS 线程(主流实现如此)
synchronized、volatile、Lock等同步机制由 JVM + CPU 内存模型 + OS 协作实现Java 内存模型(JMM)规定了可见性、有序性、原子性等规则,保证并发语义一致
7) 一句话总结 Java 代码运行原理
Java 源码先被编译成平台无关的字节码 .class,运行时由 JVM 通过类加载器加载并验证/链接/初始化,然后由解释器执行字节码;热点代码再由 JIT 编译成机器码以获得高性能;对象主要分配在堆上并由 GC 自动回收,方法调用通过线程栈栈帧管理。