一、JVM 概述
1.1 什么是 JVM?
JVM(Java Virtual Machine,Java 虚拟机)是 Java 程序运行的核心引擎。它像一个“翻译官”,将 Java 字节码转换为机器能理解的指令,并管理程序运行时的内存、线程等资源。
核心功能:
- 跨平台运行:一次编译,到处运行(Write Once, Run Anywhere)
- 内存管理:自动分配和回收内存(垃圾回收)
- 安全控制:字节码验证、权限管理
类比说明:
- Java 程序 → 一本用中文写的菜谱
- JVM → 一位精通各国语言的厨师
- 不同操作系统 → 不同国家的厨房
- 字节码 → 国际通用的菜谱符号
二、JVM 核心架构
JVM 由三大核心模块组成:
模块 | 功能 | 关键技术 |
---|---|---|
类加载系统 | 加载.class文件到内存 | 双亲委派机制 |
运行时数据区 | 管理程序运行时的内存分配 | 堆、栈、方法区 |
执行引擎 | 解释/编译字节码为机器指令 | 解释器、JIT编译器、垃圾回收 |
三、类加载机制
3.1 类加载流程
类加载分为三个阶段:
- 加载(Loading)
- 查找.class文件
- 将字节码转换为方法区的数据结构
- 链接(Linking)
- 验证:检查字节码是否符合规范
- 准备:为静态变量分配内存(默认初始值)
- 解析:将符号引用转为直接引用
- 初始化(Initialization)
- 执行静态代码块(
<clinit>
方法) - 为静态变量赋真实值
- 执行静态代码块(
示例:
public class Demo {static int value = 10; // 准备阶段 value=0,初始化阶段 value=10static {System.out.println("静态代码块执行");}
}
3.2 双亲委派模型
加载器层级:
- Bootstrap ClassLoader:加载
jre/lib
核心库(如java.lang.*) - Extension ClassLoader:加载
jre/lib/ext
扩展库 - Application ClassLoader:加载用户类路径(classpath)
- 自定义ClassLoader:用户自定义加载逻辑
工作流程:
- 子加载器收到加载请求后,先委派父加载器处理
- 父加载器无法完成时,子加载器才尝试加载
优势:
- 避免核心类被篡改(如自定义java.lang.String)
- 保证类全局唯一性
-
public class ClassLoaderDemo {public static void main(String[] args) {// 查看不同类的加载器System.out.println(String.class.getClassLoader()); // null(Bootstrap加载器)System.out.println(ClassLoaderDemo.class.getClassLoader()); // AppClassLoader} }
四、运行时数据区
4.1 内存结构总览
-
+-------------------+ | 方法区(Method Area) | ← 存储类信息、常量、静态变量 +-------------------+ | 堆(Heap) | ← 所有对象实例和数组 +-------------------+ | 虚拟机栈(VM Stack) | ← 线程私有的方法调用栈帧 | 本地方法栈(Native Stack)| ← 调用本地(Native)方法 | 程序计数器(PC Register) | ← 当前线程执行的字节码行号 +-------------------+
4.2 堆(Heap)
- 分代设计:
- 新生代(Young Generation):新创建的对象
- Eden区(80%)
- Survivor区(From + To,各10%)
- 老年代(Old Generation):长期存活的对象
- 元空间(Metaspace,JDK8+):类元数据(替代永久代)
- 新生代(Young Generation):新创建的对象
-
对象分配流程:
- 新对象优先分配到Eden区
- Eden满时触发Minor GC
- 存活对象复制到Survivor区(年龄+1)
- 年龄达到阈值(默认15)后进入老年代
示例代码:
public class HeapDemo {public static void main(String[] args) {List<byte[]> list = new ArrayList<>();while (true) {list.add(new byte[1024 * 1024]); // 持续创建1MB数组,触发OOM}}
}
// 输出:java.lang.OutOfMemoryError: Java heap space
4.3 虚拟机栈(VM Stack)
- 栈帧结构:
- 局部变量表(Local Variables)
- 操作数栈(Operand Stack)
- 动态链接(Dynamic Linking)
- 方法返回地址(Return Address)
- 栈溢出示例:
-
public class StackOverflowDemo {static void recursiveCall() {recursiveCall(); // 无限递归}public static void main(String[] args) {recursiveCall();} } // 输出:java.lang.StackOverflowError
4.4 方法区(Method Area)
- 存储内容:
- 类结构信息(字段、方法、构造函数)
- 运行时常量池
- JIT编译后的代码缓存
-
元空间溢出示例:
-
public class MetaspaceOOM {static class OOMObject {}public static void main(String[] args) {// 使用CGLIB动态生成类Enhancer enhancer = new Enhancer();enhancer.setSuperclass(OOMObject.class);enhancer.setCallback((MethodInterceptor) (obj, method, args1, proxy) -> proxy.invokeSuper(obj, args1));while (true) {enhancer.create(); // 持续生成代理类}} } // 输出:java.lang.OutOfMemoryError: Metaspace
五、垃圾回收(GC)
5.1 判断对象可回收
- 引用计数法:循环引用问题(已弃用)
- 可达性分析:从GC Roots出发,不可达的对象可回收
- GC Roots包括:
- 虚拟机栈中引用的对象
- 方法区中静态属性引用的对象
- 本地方法栈中JNI引用的对象
- GC Roots包括:
- 示例:
-
public class GCRootsDemo {Object instance;public static void main(String[] args) {GCRootsDemo a = new GCRootsDemo();GCRootsDemo b = new GCRootsDemo();a.instance = b;b.instance = a;a = null;b = null; // 此时两个对象仍互相引用,但不可达,会被回收System.gc();} }
5.2 垃圾回收算法
算法 原理 适用场景 标记-清除 标记可回收对象后清除 老年代(CMS) 复制 将存活对象复制到新空间 新生代(Serial、ParNew) 标记-整理 标记后整理内存空间 老年代(Serial Old) 分代收集 根据对象年龄采用不同算法 现代JVM默认方案
复制算法示意图:
新生代内存布局:
+-----------+------------+-----------+
| Eden | From(S0) | To(S1) |
+-----------+------------+-----------+
Minor GC后存活对象复制到To区,清空Eden和From
5.3 垃圾收集器
收集器 | 特点 | 适用场景 |
---|---|---|
Serial | 单线程,Stop-The-World | 客户端模式 |
ParNew | Serial的多线程版本 | 新生代(配合CMS) |
CMS | 并发标记清除,低停顿 | 老年代 |
G1 | 分区收集,可预测停顿时间 | JDK9+默认 |
ZGC | 低延迟(<10ms),大堆内存 | 超大内存应用 |
G1收集器示例配置
java -XX:+UseG1GC -Xmx4g -XX:MaxGCPauseMillis=200 MyApp
六、执行引擎
6.1 解释执行
- 逐行解释字节码:效率低,但启动快
- 示例:
javap -c MyClass.class
查看字节码
6.2 JIT编译(Just-In-Time)
- 热点代码检测:统计方法调用次数
- 编译优化技术:
- 方法内联(Method Inlining)
- 逃逸分析(Escape Analysis)
- 循环展开(Loop Unrolling)
逃逸分析示例:
public class EscapeAnalysisDemo {public static void main(String[] args) {for (int i = 0; i < 1000000; i++) {createObject();}}static void createObject() {Object obj = new Object(); // 对象未逃逸,可能被栈上分配}
}
6.3 分层编译(Tiered Compilation)
- 编译级别:
- 0:解释执行
- 1:简单快速编译(C1)
- 2:完全优化编译(C2)
- 参数控制:
-XX:TieredStopAtLevel=3
七、性能监控与调优
7.1 常用工具
工具 | 功能 | 示例命令 |
---|---|---|
jps | 查看Java进程ID | jps -l |
jstat | 监控GC情况 | jstat -gcutil <pid> 1000 |
jmap | 生成堆转储快照 | jmap -dump:format=b,file=heap.bin <pid> |
VisualVM | 图形化性能分析 | 监控CPU、内存、线程 |
jstat输出示例:
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
0.00 100.00 25.43 68.50 95.12 91.03 10 0.123 2 0.456 0.579
7.2 常见JVM参数
参数 | 作用 | 示例值 |
---|---|---|
-Xms / -Xmx | 初始/最大堆内存 | -Xms512m -Xmx4g |
-XX:NewRatio | 新生代与老年代比例 | -XX:NewRatio=3 (新生代占1/4) |
-XX:SurvivorRatio | Eden与Surviv区比例 | -XX:SurvivorRatio=8 (Eden:S0:S1=8:1:1) |
-XX:+PrintGCDetails | 打印详细GC日志 |
八、实战案例
8.1 内存泄漏排查
步骤:
- 使用
jps
获取进程ID jmap -dump:format=b,file=heap.bin <pid>
导出堆快照- 用MAT(Memory Analyzer Tool)分析
- 查找支配树中的大对象
常见原因:
- 静态集合类持有对象引用
- 未关闭的数据库连接
- 监听器未注销
8.2 GC优化案例
问题现象:Full GC频繁,每次耗时2秒
分析步骤:
jstat -gcutil <pid> 1000
发现老年代快速填满jmap -histo <pid>
发现大量相同类实例- 检查代码发现缓存未设置上限
解决方案:改用LRU缓存,限制最大条目数
九、JVM发展前沿
9.1 GraalVM
- 多语言支持:Java、JavaScript、Python等
- 原生镜像(Native Image):提前编译为本地可执行文件
- 示例:
native-image -jar myapp.jar
9.2 Project Loom
- 虚拟线程(Virtual Threads):轻量级线程,支持百万级并发
- 示例:
-
Thread.startVirtualThread(() -> {System.out.println("Hello from virtual thread!"); });
9.3 ZGC与Shenandoah
- 亚毫秒级停顿:适用于金融交易系统
- 配置示例
-
java -XX:+UseZGC -Xmx16g -XX:+UseLargePages MyApp