1. 概述
JVM 是史上最古老且最强大的虚拟机之一。本文将快速解析 JVM 预热的含义及实现方法。
2. JVM 架构基础
每当新的 JVM 进程启动时,所有必需的类会通过 ClassLoader 实例加载到内存中。该过程分三步:
- 引导类加载:引导类加载器将 Java 核心类(如
java.lang.Object
)加载到内存。这些类位于JRE/lib/rt.jar
。 - 扩展类加载:ExtClassLoader 负责加载
java.ext.dirs
路径下的所有 JAR 文件。在非 Maven/Gradle 项目中,开发者手动添加的 JAR 会在该阶段加载。 - 应用类加载:AppClassLoader 加载应用类路径下的所有类。
⚠️ 初始化过程采用懒加载机制。
3. 什么是 JVM 预热
类加载完成后,关键类(进程启动时使用的类)会被推入 JVM 缓存(原生代码),使运行时访问更快。其他类按需加载。
Java Web 应用的首次请求通常比后续请求慢得多,这种预热期主要归因于懒加载和即时编译(JIT)。
对于低延迟应用,需预先缓存所有类——这个调优过程称为预热。
4. 分层编译
得益于 JVM 的优秀架构,高频方法会在应用生命周期中被加载到原生缓存。我们可以利用此特性在启动时强制加载关键方法,需设置 VM 参数:
-XX:CompileThreshold -XX:TieredCompilation
VM 通常通过解释器收集方法分析信息供编译器使用。在分层编译模式下,除解释器外,客户端编译器会生成方法的编译版本并收集自身分析信息。
由于编译代码远快于解释代码,程序在分析阶段性能更佳。
❌ 踩坑:JBoss + JDK 7 启用此参数后可能因已知 bug 崩溃,JDK 8 已修复。
✅ 关键点:需确保所有(或大部分)待执行类被访问,类似单元测试的代码覆盖率——覆盖越多,性能越好。
5. 手动实现
可通过手动技术预热 JVM:应用启动时重复创建不同类数千次。
首先创建一个带普通方法的虚拟类:
public class Dummy {
public void m() {
}
}
再创建一个静态方法类,启动时执行至少 100,000 次,每次创建虚拟类实例:
public class ManualClassLoader {
protected static void load() {
for (int i = 0; i < 100000; i++) {
Dummy dummy = new Dummy();
dummy.m();
}
}
}
为测量性能提升,创建主类。静态块直接调用 load()
,main 方法再次调用并计时:
public class MainApplication {
static {
long start = System.nanoTime();
ManualClassLoader.load();
long end = System.nanoTime();
System.out.println("Warm Up time : " + (end - start));
}
public static void main(String[] args) {
long start = System.nanoTime();
ManualClassLoader.load();
long end = System.nanoTime();
System.out.println("Total time taken : " + (end - start));
}
}
运行两次(一次带静态块预热,一次不带),结果对比(单位:纳秒):
预热状态 | 执行时间 1 | 执行时间 2 | 执行时间 3 | 执行时间 4 | 执行时间 5 |
---|---|---|---|---|---|
✅ 预热 | 1,220,056 | 1,083,797 | 1,026,025 | 1,024,047 | 868,782 |
❌ 未预热 | 8,903,640 | 13,609,530 | 9,283,837 | 7,234,871 | 9,146,180 |
提速 | 730% | 1256% | 905% | 706% | 1053% |
⚠️ 这是极简基准测试,实际应用需预热典型代码路径。
6. 工具支持
可使用专业工具预热 JVM,如 Java 微基准测试套件 JMH。它通过反复执行代码片段监控预热迭代周期。
添加 Maven 依赖:
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>1.37</version>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>1.37</version>
</dependency>
或用 Maven 插件生成项目:
mvn archetype:generate \
-DinteractiveMode=false \
-DarchetypeGroupId=org.openjdk.jmh \
-DarchetypeArtifactId=jmh-java-benchmark-archetype \
-DgroupId=com.baeldung \
-DartifactId=test \
-Dversion=1.0
创建主方法:
public static void main(String[] args)
throws RunnerException, IOException {
Main.main(args);
}
用 @Benchmark
注解标记需重复执行的方法:
@Benchmark
public void init() {
// 预热代码片段
}
7. 性能基准
过去 20 年 Java 的改进主要聚焦 GC 和 JIT。多数在线基准测试在已运行的 JVM 上进行,但北京航空航天大学发布的报告考虑了预热时间:
图中 HotTub 表示已预热的 JVM 环境。可见小规模读操作提速显著。
8. 总结
本文解析了 JVM 启动时的类加载机制及预热方法。深入探讨可参考《Java性能:权威指南》。
完整源码见 GitHub。