通过 HelloWorld 深入剖析 JVM 启动过程
通过 HelloWorld 深入剖析 JVM 启动过程
在 Java 开发中,理解 JVM 的启动过程对于调试和性能优化至关重要。本文将通过一个简单的 HelloWorld 示例,深入探讨从运行 java 命令到应用程序启动的全过程,帮助开发人员更好地理解 JVM 的内部机制。
文章目录
- 通过 HelloWorld 深入剖析 JVM 启动过程
- 1. 概述
- 2. 从 Java 命令到 JVM 启动
- 2.1 Java 命令与初始调用
- 2.2 参数验证
- 2.3 系统资源检测
- 2.4 环境准备
- 3. 加载、链接和初始化
- 3.1 垃圾回收器选择
- 3.2 缓存数据存储加载
- 3.3 创建方法区
- 3.4 类加载
- 3.5 类链接
- 3.6 类初始化
- 4. 优化 JVM 启动性能
- 4.1 类加载的影响
- 4.2 莱顿计划(Project Leyden)
- 4.3 JVM 标志和调优
- 5. 结论
1. 概述
运行 Java 应用程序时,JVM 会执行一系列复杂的初始化步骤,这些步骤为 Java 应用程序的运行奠定了基础。深入理解这些步骤可以显著提升我们在调试和性能调优方面的效率。
2. 从 Java 命令到 JVM 启动
2.1 Java 命令与初始调用
当我们运行 java 命令时,JVM 启动序列首先调用 JNI 方法 JNI_CreateJavaVM()。该方法负责执行必要的初始化任务,为 Java 应用程序的运行做好准备。JNI(Java Native Interface)作为 JVM 与本地系统库之间的桥梁,实现了 Java 代码与平台特定功能之间的无缝双向通信。
java -Xlog:all=trace HelloWorld
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
2.3 系统资源检测
接下来,JVM 会识别可用的系统资源,例如处理器数量、内存大小和关键系统服务。这些信息指导 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.
2.4 环境准备
然后,JVM 通过生成 HotSpot 性能数据来准备运行时环境。这些数据会被 JConsole 和 VisualVM 等性能分析工具使用。
[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
3. 加载、链接和初始化
3.1 垃圾回收器选择
JVM 内部的一个关键步骤是选择垃圾回收器。从 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
3.2 缓存数据存储加载
JVM 开始寻求优化。CDS(Class Data Sharing)是一个已经过预处理的类文件归档,它可以显著提高 JVM 的启动性能。
[cds] trying to map [Java home]/lib/server/classes.jsa
[cds] Opened archive [Java home]/lib/server/classes.jsa
3.3 创建方法区
JVM 随后会创建方法区,这是一个特殊的堆外内存区域,用于存储类数据。HotSpot JVM 实现将此区域称为元空间。
[metaspace,map ] Trying anywhere...
[metaspace,map ] Mapped at 0x000001f32b000000
3.4 类加载
类加载是一个三步过程:找到类的二进制表示、从中派生出类,并将其加载到方法区。
public class HelloWorld extends Object {public static void main(String[] args) {System.out.println("Hello World!");}
}
3.5 类链接
类链接包含三个子过程——验证、准备和解析。这些步骤并非按顺序进行,解析可以在验证之前、类初始化之后的任何时间发生。
[class,init] Start class verification for: HelloWorld
[verification] Verifying class HelloWorld with new format
[verification] Verifying method HelloWorld.<init>()V
3.6 类初始化
类初始化会为静态字段赋值并执行静态初始化器。
4. 优化 JVM 启动性能
4.1 类加载的影响
为了测量 JVM 启动、加载类、链接类并执行我们简单程序所需的总时间,我们可以使用系统的时间实用程序。
time java HelloWorld
4.2 莱顿计划(Project Leyden)
莱顿计划旨在缩短启动时间、缩短达到峰值性能所需的时间并减少内存占用。JDK 24 引入了 JEP 483:提前类加载和链接,它会在启动前而不是启动时执行这些操作,从而显著提升启动性能。
4.3 JVM 标志和调优
虽然可以通过使用静态字段和初始化器来优化启动性能,但我们应该谨慎对待。大部分执行的代码来自依赖项,而不是我们自己的应用程序代码。因此,合理使用 JVM 标志(如 -XX:+UseG1GC 和 -XX:MaxMetaspaceSize)可以进一步优化性能。
5. 结论
本文深入剖析了 JVM 在启动过程中经历的复杂流程,从验证用户输入、检测系统资源到加载、链接和初始化类。即使是简单的 HelloWorld 应用程序,JVM 也会在执行代码之前准备整个运行时环境,加载数百个类。随着 Project Leyden 的 AOT 功能等改进措施的推出,启动性能将持续提升。对于开发人员而言,理解这些机制不仅有助于调试和性能优化,还能帮助我们更好地设计和优化 Java 应用程序。
