1. 概述

JVM 是史上最古老且最强大的虚拟机之一。本文将快速解析 JVM 预热的含义及实现方法。

2. JVM 架构基础

每当新的 JVM 进程启动时,所有必需的类会通过 ClassLoader 实例加载到内存中。该过程分三步:

  1. 引导类加载:引导类加载器将 Java 核心类(如 java.lang.Object)加载到内存。这些类位于 JRE/lib/rt.jar
  2. 扩展类加载:ExtClassLoader 负责加载 java.ext.dirs 路径下的所有 JAR 文件。在非 Maven/Gradle 项目中,开发者手动添加的 JAR 会在该阶段加载。
  3. 应用类加载: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 上进行,但北京航空航天大学发布的报告考虑了预热时间:

jvm性能对比

图中 HotTub 表示已预热的 JVM 环境。可见小规模读操作提速显著。

8. 总结

本文解析了 JVM 启动时的类加载机制及预热方法。深入探讨可参考《Java性能:权威指南》

完整源码见 GitHub


原始标题:How to Warm Up the JVM