深入探索剖析 JVM 的启动过程

2025-12-12 0 769

你可曾想过:当你在终端里敲下 java,在 main 方法真正运行之前,JVM 为了“创造一个可运行你的程序的宇宙”,到底经历了哪些步骤?从参数校验、系统资源探测,到选择垃圾回收器,再到类的加载、链接与初始化,这些看不见的过程决定了应用的启动体验与后续性能。本文用一个极简的 HelloWorld 贯穿全程,结合详细日志,一步步洞察 JVM 的启动机制,帮你在调试和性能优化时更有抓手。

1. 概览

当我们运行一个 Java 应用时,JVM 在我们的代码真正开始执行之前,会先完成一系列复杂步骤。本文将从执行 java 命令的那一刻开始,一直走到应用就绪。

我们以一个简单的 HelloWorld 程序为例,拆解每一个阶段。理解这些内部机制能显著提升调试与性能调优的效果。

2. 从 java 命令到 JVM 启动

在 JVM 执行任何代码之前,它需要先启动、校验输入并配置运行环境。下面按启动顺序走一遍早期流程:从调用 java 命令到初始化 JVM 运行时。

2.1. java 命令与初始调用

当我们运行 java 命令时,JVM 启动序列会通过 JNI 方法 JNI_CreateJavaVM() 开始执行。该方法完成若干关键初始化任务,为执行 Java 应用准备环境。Java Native Interface(JNI)是 JVM 与原生系统库之间的桥梁,使 Java 与平台特性可以双向通信。

本文将使用详细日志观察 JVM 的内部运作,例如:

java -Xlog:all=trace HelloWorldCopy

2.2. 校验用户输入

首先,JVM 会校验我们传入的参数:

[0.006s][info][arguments] VM Arguments:
[arguments] jvm_args: -Xlog:all=trace:file=helloworld.log 
[arguments] java_command: HelloWorld
[arguments] java_class_path (initial): .
[arguments] Launcher Type: SUN_STANDARD

JVM 会验证目标可执行、类路径以及任何 JVM 参数,确保它们在继续执行前都是有效的。这个步骤能尽早捕获很多常见配置错误,避免后续阶段出现更难定位的问题。

2.3. 检测系统资源

接着,JVM 会识别可用的系统资源,例如处理器数量、内存大小以及关键系统服务:

[0.007s][debug][os       ] Process is running in a job with 20 active processors.
[os       ] Initial active processor count set to 20
[os       ] Process is running in a job with 20 active processors.
[gc,heap  ]   Maximum heap size 4197875712
[gc,heap  ]   Initial heap size 262367232
[gc,heap  ]   Minimum heap size 6815736
[os       ] Host Windows OS automatically schedules threads across all processor groups.
[os       ] 20 logical processors found.

这些信息会影响 JVM 的一些内部决策,比如默认选择哪个垃圾回收器。可用 CPU 数和总内存会直接影响 JVM 的启发式选择。不过,大多数设置都可以通过显式的 JVM 参数进行覆盖。在这个阶段,JVM 还会检查是否支持 Native Memory Tracking,并验证它可能依赖的各类操作系统工具的可用性。

2.4. 环境准备

随后,JVM 会生成 HotSpot 性能数据。这些数据会被 JConsole、VisualVM 等工具用于检查和分析 JVM:

[perf,datacreation] name = sun.rt._sync_Inflations, dtype = 11, variability = 2, units = 4, dsize = 8, vlen = 0, pad_length = 4, size = 56, on_c_heap = FALSE, address = 0x000001f3085f0020, data address = 0x000001f3085f0050

这类性能数据通常存储在系统的 /tmp 目录下,并会在启动阶段的一段时间里持续生成,与其他初始化任务并行进行。

3. 加载、链接与初始化

当 JVM 环境就绪后,它会开始为我们的程序执行做准备。

3.1. 选择垃圾回收器

在 JVM 内部,一个关键步骤是选择垃圾回收器(GC)。截至 JDK 23,默认情况下 JVM 会选择 G1 GC,除非系统可用内存少于 1792MB 和/或仅有单处理器:

[gc               ] Using G1
[gc,heap,coops    ] Trying to allocate at address 0x0000000705c00000 heap of size 0xfa400000
[os               ] VirtualAlloc(0x0000000705c00000, 4198498304, 2000, 4) returned 0x0000000705c00000.
[os,map           ] Reserved [0x0000000705c00000 - 0x0000000800000000), (4198498304 bytes)
[gc,heap,coops    ] Heap address: 0x0000000705c00000, size: 4004 MB, Compressed Oops mode: Zero based, Oop shift amount: 3
[pagesize         ] Heap:  min=8M max=4004M base=0x0000000705c00000 size=4004M page_size=4K

当然,我们也可以选择其它 GC:如 Parallel GC、ZGC 等,具体可用与默认策略依不同 JDK 版本与发行版而异。

3.2. 加载 CDS(类数据共享)

此时,JVM 会开始寻找进一步的优化机会。CDS 是一组已经经过预处理的类文件归档,可以改善 JVM 的启动性能:

[cds] trying to map [Java home]/lib/server/classes.jsa
[cds] Opened archive [Java home]/lib/server/classes.jsa

不过,CDS 正在被 Project Leyden 中的 AOT(提前)机制逐步替代,后文会继续讨论。

3.3. 创建方法区

JVM 随后会创建“方法区”,这是一个用于存储类数据的特殊离堆内存区域。在 HotSpot 中,这一区域被称为 metaspace。当关联的类加载器不再可达时,存储于此的类数据也会被移除:

[metaspace,map    ] Trying anywhere...
[metaspace,map    ] Mapped at 0x000001f32b000000

虽然方法区不在堆中,但它仍由 GC 管理。

3.4. 类加载

类加载包含三个步骤:定位二进制表示、根据其派生出类、并将其加载到方法区。正是这种动态加载能力,让 Spring、Mockito 等框架可以在运行期按需生成并加载类。

类加载有两种方式:引导类加载器(bootstrap class loader)或自定义类加载器。下面借助一个简单的 HelloWorld 类,看看 JVM 首先会做什么:

public class HelloWorld extends Object {
    public static void main(String[] args) {
        System.out.println(\"Hello World!\");
    }
}

JVM 会优先加载 java.lang.Object 及其依赖。类在初次加载时大多处于“半隐藏”的状态,以便进行必要的验证与整理工作。

再看下 java.lang.Object 的方法:

public class Object {
    public final native Class getClass()
    public String toString()
    public boolean equals(Object obj)
}

这些方法分别引用了 java.lang.Classjava.lang.String,因此它们也需要先行加载。JVM 采用“按需加载”的策略,仅在类被实际引用时才加载。不过,上述这些对 JVM 至关重要的类会被“抢先加载”。在一个简单的 HelloWorld 程序里,由 JNI_CreateJavaVM() 初始化的引导类加载器负责所有的类加载工作。

3.5. 类链接

类链接可以拆分为验证(Verification)、准备(Preparation)与解析(Resolution),其发生顺序并不固定:解析可能发生在验证之前,也可能在类初始化之后。验证确保类结构正确:

[class,init] Start class verification for: HelloWorld
[verification] Verifying class HelloWorld with new format
[verification] Verifying method HelloWorld.()V

位于 CDS 中的类已经过验证,因此会跳过该步骤,从而提升启动性能。这是 CDS 的重要收益之一。在“准备”阶段,JVM 会用默认值初始化静态字段,没有显式初始化器的静态变量会自动获得默认值。

在“解析”阶段,JVM 会解析常量池(Constant Pool)中的符号引用。常量池保存了类的所有符号引用,JVM 必须先将其解析为真实的内存引用,才能执行相关指令。

我们可以使用 javap 来观察:

javap -verbose HelloWorldCopy

这将显示常量池的内容:

Constant pool:
   #1 = Methodref          #2.#3          // java/lang/Object.\"\":()V
   #2 = Class              #4             // java/lang/Object
   #3 = NameAndType        #5:#6          // \"\":()V
   #7 = Fieldref           #8.#9          // java/lang/System.out:Ljava/io/PrintStream;
  #13 = String             #14            // Hello World

构造器的字节码并不直接包含地址。它引用常量池中的符号项(例如 #1),这些条目描述了方法或字段。解析阶段会将这些符号项转为可执行的真实内存引用:

public HelloWorld();
  descriptor: ()V
  flags: (0x0001) ACC_PUBLIC
  Code:
    stack=1, locals=1, args_size=1
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object.\"\":()V
       4: return
  LineNumberTable:
    line 2: 0
    line 4: 4

第 1 行的 invokespecial 指令引用了常量池条目 #1,其中包含链接到 java.lang.Object 构造器所需的信息。 表示这是由 javac 为每个构造器自动生成的特殊方法。JVM 采用“延迟解析”,只有在尝试执行类中的某条指令时才触发解析;并非所有已加载的类都会实际执行其指令。

3.6. 类初始化

类初始化会为静态字段赋值并执行静态初始化器,这与我们调用构造器的实例初始化不同。该过程由 javac 自动生成的特殊方法 clinit 负责。

4. 优化 JVM 启动性能

尽管 JVM 的启动已经很高效,但仍有提升空间。以下是一些方向。

4.1. 类加载的影响

我们可以使用系统的 time 工具来度量 JVM 启动、加载类、链接并执行这个简单程序的总耗时:

time java HelloWorldCopy

该工具会测量从 JVM 进程启动到退出的挂钟时间,包含类加载、链接、JIT 预热与程序执行——不仅仅是用户代码。对于 HelloWorld,JVM 在启动期间通常会加载约 400~450 个类。在现代硬件上,即便开启冗长日志,整个过程也大约在 60 毫秒左右完成。

4.2. Project Leyden

Project Leyden 的目标是减少启动时间、达到峰值性能的时间以及内存占用。JDK 24 引入了 JEP 483:Ahead-of-Time Class Loading and Linking(提前类加载与链接),将这些操作从启动时前移至 AOT 阶段。

该特性会在“训练运行”中记录 JVM 的行为,将其存入缓存,并在后续启动时从缓存加载。这将取代原先的 CDS 概念,并最终以 AOT 的更广泛能力来统一表达。

4.3. JVM 参数与调优

虽然我们可以通过静态字段与初始化器在某些场景中优化启动性能,但应谨慎对待。为了将行为挪到类加载阶段而进行重构,往往很难获得可测量的收益——特别是考虑到运行时的大部分代码来自依赖库而非我们自己的应用。

5. 结论

本文从校验用户输入、检测系统资源,到类的加载、链接与初始化,系统地梳理了 JVM 在启动阶段经历的复杂流程。即便是一个简单的 HelloWorld,JVM 也会在执行代码之前构建起完整的运行环境,加载数百个类。

随着 Project Leyden 等改进(例如 AOT)的

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

申明:本文由第三方发布,内容仅代表作者观点,与本网站无关。对本文以及其中全部或者部分内容的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。本网发布或转载文章出于传递更多信息之目的,并不意味着赞同其观点或证实其描述,也不代表本网对其真实性负责。

左子网 编程相关 深入探索剖析 JVM 的启动过程 https://www.zuozi.net/35998.html

常见问题
  • 1、自动:拍下后,点击(下载)链接即可下载;2、手动:拍下后,联系卖家发放即可或者联系官方找开发者发货。
查看详情
  • 1、源码默认交易周期:手动发货商品为1-3天,并且用户付款金额将会进入平台担保直到交易完成或者3-7天即可发放,如遇纠纷无限期延长收款金额直至纠纷解决或者退款!;
查看详情
  • 1、描述:源码描述(含标题)与实际源码不一致的(例:货不对板); 2、演示:有演示站时,与实际源码小于95%一致的(但描述中有”不保证完全一样、有变化的可能性”类似显著声明的除外); 3、发货:不发货可无理由退款; 4、安装:免费提供安装服务的源码但卖家不履行的; 5、收费:价格虚标,额外收取其他费用的(但描述中有显著声明或双方交易前有商定的除外); 6、其他:如质量方面的硬性常规问题BUG等。 注:经核实符合上述任一,均支持退款,但卖家予以积极解决问题则除外。
查看详情
  • 1、左子会对双方交易的过程及交易商品的快照进行永久存档,以确保交易的真实、有效、安全! 2、左子无法对如“永久包更新”、“永久技术支持”等类似交易之后的商家承诺做担保,请买家自行鉴别; 3、在源码同时有网站演示与图片演示,且站演与图演不一致时,默认按图演作为纠纷评判依据(特别声明或有商定除外); 4、在没有”无任何正当退款依据”的前提下,商品写有”一旦售出,概不支持退款”等类似的声明,视为无效声明; 5、在未拍下前,双方在QQ上所商定的交易内容,亦可成为纠纷评判依据(商定与描述冲突时,商定为准); 6、因聊天记录可作为纠纷评判依据,故双方联系时,只与对方在左子上所留的QQ、手机号沟通,以防对方不承认自我承诺。 7、虽然交易产生纠纷的几率很小,但一定要保留如聊天记录、手机短信等这样的重要信息,以防产生纠纷时便于左子介入快速处理。
查看详情

相关文章

猜你喜欢
发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务