JVM 入门与概览:Java 程序到底是如何运行起来的?
本文是 JVM 系列第一篇,适合 Java 初学者以及工作 1~3 年的后端开发工程师阅读。
一、前言
很多 Java 开发者每天都在写代码:
public class Main {
public static void main(String[] args) {
System.out.println("Hello JVM");
}
}
程序运行后输出:
Hello JVM
看起来非常简单。
但实际上,在这行代码背后,JVM 做了大量工作:
- 加载类文件
- 验证字节码
- 分配内存
- 创建线程
- 执行方法
- 回收垃圾对象
- 编译热点代码
很多面试官喜欢问:
Java 为什么能够跨平台?
JVM 和 JDK 有什么区别?
Java 程序到底是如何运行起来的?
本文将带你从零开始理解 JVM。
二、什么是 JVM?
JVM 全称:
Java Virtual Machine(Java 虚拟机)
它本质上是一个运行 Java 字节码(ByteCode)的虚拟计算机。
简单理解:
Java代码
↓
编译
↓
.class字节码
↓
JVM执行
↓
操作系统
↓
CPU
例如:
int a = 10;
int b = 20;
System.out.println(a + b);
Java 编译器(javac)不会直接生成 CPU 可以执行的机器码。
而是生成:
Main.class
然后 JVM 再负责解释或编译执行。
三、为什么 Java 能跨平台?
Java 有一句经典口号:
Write Once, Run Anywhere
一次编写,到处运行
原因就在 JVM。
C语言运行方式
C源码
↓
编译
↓
Windows机器码
↓
Windows运行
如果换成 Linux:
C源码
↓
重新编译
↓
Linux机器码
↓
Linux运行
必须重新编译。
Java运行方式
Java源码
↓
javac
↓
字节码(.class)
↓
JVM
↓
操作系统
只要安装对应平台 JVM:
Windows JVM
Linux JVM
Mac JVM
同一个 class 文件都能运行。
例如:
Hello.class
既可以在:
- Windows
- Linux
- MacOS
直接运行。
这就是 Java 跨平台的核心原因。
四、JDK、JRE、JVM 三者关系
这是面试高频题。
很多同学工作几年都说不清。
JVM
Java 虚拟机
负责:
- 加载字节码
- 执行字节码
- 内存管理
- 垃圾回收
简单理解:
JVM = Java运行引擎
JRE
Java Runtime Environment
Java运行环境
包含:
JVM
+
Java核心类库
例如:
String
ArrayList
HashMap
这些类都来自 JRE。
JDK
Java Development Kit
Java开发工具包
包含:
JDK
├─ JRE
│ └─ JVM
│
├─ javac
├─ javadoc
├─ jstack
├─ jmap
├─ jstat
└─ jconsole
因此:
JDK > JRE > JVM
关系图:
JDK
│
├── JRE
│ │
│ ├── JVM
│ └── Java基础类库
│
├── javac
├── jmap
├── jstack
└── jstat
五、Java 程序运行全过程
下面是 JVM 最核心的流程。
第一步:编写源码
public class Main {
public static void main(String[] args) {
System.out.println("Hello JVM");
}
}
保存:
Main.java
第二步:编译
执行:
javac Main.java
生成:
Main.class
第三步:加载字节码
执行:
java Main
JVM启动。
此时:
ClassLoader
↓
加载 Main.class
进入 JVM 内存。
第四步:验证字节码
JVM 会检查:
字节码是否合法
格式是否正确
是否存在安全风险
例如:
非法修改class文件
会直接拒绝加载。
第五步:创建运行时内存
JVM 创建:
方法区
堆
虚拟机栈
程序计数器
本地方法栈
后面章节会详细讲解。
第六步:执行 main 方法
执行引擎开始工作:
main()
↓
println()
↓
输出结果
最终:
Hello JVM
出现在控制台。
六、JVM 核心组成部分
很多人学习 JVM 时容易迷失。
实际上 JVM 核心模块只有几个。
整体结构
Java Program
│
▼
Class Loader
│
▼
Runtime Data Area
│
▼
Execution Engine
│
▼
Native Method
│
▼
Operating System
七、类加载器(Class Loader)
作用:
加载class文件
例如:
new User();
JVM发现:
User.class
还没加载。
于是:
ClassLoader
↓
加载User.class
↓
放入内存
供程序使用。
八、运行时数据区(Runtime Data Area)
这是 JVM 内存区域。
包含:
程序计数器
虚拟机栈
本地方法栈
堆
方法区
后续章节会重点讲。
先简单理解:
堆(Heap)
对象存储区。
例如:
new User();
new ArrayList();
new HashMap();
全部放在堆中。
栈(Stack)
方法运行区域。
例如:
public void test() {
int age = 18;
}
变量:
age
存在栈中。
九、执行引擎(Execution Engine)
执行引擎负责真正运行代码。
主要有两种方式:
解释执行
逐行翻译执行:
字节码
↓
解释器
↓
机器码
优点:
启动快
缺点:
执行慢
JIT编译执行
热点代码:
for(int i=0;i<10000000;i++)
执行很多次后。
JVM发现:
这段代码经常运行
于是:
字节码
↓
JIT
↓
机器码
直接编译。
以后直接执行机器码。
速度大幅提升。
十、垃圾回收器(GC)
Java 最伟大的设计之一。
开发者不用:
free()
delete
手动释放内存。
例如:
User user = new User();
user = null;
对象失去引用。
GC 会自动回收。
GC 负责:
发现垃圾对象
回收内存
整理空间
避免内存泄漏
后面我们会单独用数篇文章讲解。
十一、为什么 JVM 如此重要?
很多程序员认为:
我只写业务代码,不需要学 JVM。
实际上:
当线上出现问题时:
CPU飙高
频繁GC
内存溢出
接口超时
线程阻塞
服务卡死
最后都绕不开 JVM。
例如:
OutOfMemoryError
StackOverflowError
GC Overhead Limit Exceeded
Metaspace OOM
这些都是 JVM 问题。
高级开发工程师必须掌握。
十二、面试高频问题
1 JVM 是什么?
Java Virtual Machine,负责加载和执行 Java 字节码。
2 Java 为什么跨平台?
因为 JVM 屏蔽了底层操作系统差异。
.class
↓
JVM
↓
Windows/Linux/Mac
3 JDK、JRE、JVM 的关系?
JDK > JRE > JVM
JDK = 开发工具 + JRE
JRE = JVM + Java类库
JVM = 虚拟机
4 JVM 的核心组成?
类加载器
运行时数据区
执行引擎
本地方法接口
垃圾回收器
5 JVM 和 JMM 有什么区别?
JVM:
Java Virtual Machine
Java虚拟机。
JMM:
Java Memory Model
Java内存模型。
主要解决:
线程可见性
有序性
原子性
问题。
总结
本文我们建立了 JVM 的整体认知框架:
Java源码
↓
javac编译
↓
.class字节码
↓
ClassLoader加载
↓
运行时数据区
↓
执行引擎
↓
JIT优化
↓
GC回收
掌握这一张图,后续学习 JVM 就不会迷路。
下一篇:
《第二章:类加载机制与类生命周期 —— 双亲委派模型到底解决了什么问题?》
我们将深入分析:
- 类加载器体系
- 双亲委派机制
- 类加载全过程
- 类初始化时机
- 面试经典题:为什么重写 String 类没用?