一个Java的main方法在JVM中的执行流程

一个Java的main方法在JVM中的执行流程可以分为​​四大阶段​ ​:​​加载 -> 链接 -> 初始化 -> 执行​​。

复制代码
// HelloWorld.java
public class HelloWorld {
    public static void main(String[] args) {
        String message = "Hello, JVM!";
        System.out.println(message);
    }
}

第一阶段:加载 (Loading)

​目标:找到并加载类的二进制数据。​

    1. ​编译​ ​:你执行 javac HelloWorld.java。Java编译器将源代码编译成JVM能理解的字节码,存储在 HelloWorld.class文件中。这个文件包含了一个​​类常量池(Constant Pool)​ ​,里面有各种符号引用,比如 Hello, JVM!这个字符串的字面量、System/out/println等类名、方法名和字段名。
    1. ​启动JVM​ ​:你执行 java HelloWorld。操作系统会启动JVM进程。
    1. ​寻找类​ ​:JVM通过​​类加载器(ClassLoader)​ ​ 来加载 HelloWorld类。
    • • ​​ Bootstrap ClassLoader​ ​:首先,启动类加载器会去加载JAVA_HOME/lib下的核心类库,如 java.lang包(包括Object, String, System等)。

    • • ​​ Application ClassLoader​ ​:然后,应用程序类加载器开始工作,它在你的CLASSPATH(默认是当前目录)下寻找 HelloWorld.class文件。

    1. ​创建Class对象​ ​:JVM成功读取 HelloWorld.class的二进制字节流后,会将其转换为​​方法区(Metaspace)​ ​ 中的运行时数据结构,并同时在 ​​Java堆(Heap)​ ​ 中创建一个 java.lang.Class对象,作为方法区这些数据的访问入口。这个 Class对象封装了类的所有元信息(如方法、字段等)。

第二阶段:链接 (Linking)

​目标:将加载到方法区的二进制数据合并到JVM运行时状态中。​​ 此阶段细分为三步:

    1. ​验证 (Verification)​ ​:JVM会严格检查 HelloWorld.class文件的格式、元数据、字节码等是否符合规范且不会危害JVM自身安全。这是一个非常重要的安全屏障。
    1. ​准备 (Preparation)​ ​:JVM为​​类的静态变量(static variables)​ ​ 在方法区分配内存并设置​​初始值​​(零值)。注意,这里是初始值,不是代码中赋的值。
    • • 例如,如果类里有 static int value = 123;,在准备阶段,value会被赋值为 0。真正的赋值 123要等到后面的初始化阶段。
    1. ​解析 (Resolution)​ ​:JVM将​​类常量池​ ​中的​​符号引用(Symbolic References)​ ​ 替换为​​直接引用(Direct References)​​。
    • • ​​符号引用​ ​:就是一种约定好的形式来表示引用的目标,比如 java/lang/System.out

    • • ​​直接引用​​:就是一个直接指向目标的指针、偏移量或句柄。

    • • 例如,在这一步,System.out这个符号引用会被解析为 java.io.PrintStream对象在堆内存中的实际地址。


第三阶段:初始化 (Initialization)

​目标:执行类的构造器 <clinit>()方法,为静态变量赋予程序设定的初始值。​

    1. 到了这一步,JVM才开始真正执行你写在Java代码中的静态语句和静态变量赋值。
    1. JVM会收集类中的所有​​静态变量的赋值动作​ ​和​​静态代码块(static {})​ ​,合并生成一个唯一的 <clinit>()方法。
    1. JVM会确保 <clinit>()方法在多线程环境下被正确地加锁同步执行,所以类初始化是线程安全的。
    1. 在我们的 HelloWorld例子中,没有静态变量和静态代码块,所以 <clinit>()方法是空的,但这一步依然会发生。

第四阶段:执行 (Execution & Runtime)

​目标:创建线程,执行字节码。​

    1. ​主线程​ ​:JVM会为 main方法创建一个​​主线程​ ​。该线程拥有自己的​​程序计数器(PC)​ ​ 和 ​​Java虚拟机栈(JVM Stack)​​。
    1. ​栈帧​ ​:线程的每个方法调用都会在虚拟机栈中创建一个​​栈帧(Stack Frame)​ ​,用于存储​​局部变量表​ ​、​​操作数栈​ ​、​​动态链接​ ​、​​方法返回地址​ ​等信息。main方法是程序入口,所以第一个被压入栈的栈帧就是 main方法的栈帧。
    1. ​执行引擎​ ​:JVM的​​执行引擎​ ​开始解释执行 main方法栈帧中的字节码。
    • String message = "Hello, JVM!";

      • • 执行引擎遇到字面量 "Hello, JVM!"时,会去​​字符串常量池(String Table,位于堆中)​ ​ 中寻找。如果找不到,就在堆中创建一个String对象并将其引用驻留在常量池中,然后将该引用存入 main栈帧的局部变量表 message中。
    • System.out.println(message);

      • • 执行引擎通过之前在​​解析阶段​ ​已经转换好的​​直接引用​ ​,快速地找到 System.out对应的 PrintStream对象。

      • • 然后调用该对象的 println方法,将局部变量 message的引用(指向堆中的String对象)作为参数传入。

    1. ​本地方法调用​ ​:println方法底层是一个​​本地方法(Native Method)​​,调用的是操作系统本身的IO能力,将字符串输出到控制台。
    1. ​方法返回​ ​:main方法执行完毕,其栈帧从虚拟机栈中弹出。主线程结束。
    1. ​JVM退出​ ​:所有​​非守护线程​​都结束后,JVM进程终止。
相关推荐
江团1io04 小时前
深入解析三色标记算法
java·开发语言·jvm
天天摸鱼的java工程师5 小时前
RestTemplate 如何优化连接池?—— 八年 Java 开发的踩坑与优化指南
java·后端
m0_738120725 小时前
CTFshow系列——PHP特性Web97-100
开发语言·安全·web安全·php·ctfshow
你我约定有三5 小时前
java--泛型
java·开发语言·windows
一枝花算不算浪漫5 小时前
线上频繁FullGC?慌得一比!竟是Log4j2的这个“特性”坑了我
jvm·后端
杨杨杨大侠5 小时前
第3章:实现基础事件总线
java·github·eventbus
杨杨杨大侠5 小时前
第4章:添加注解支持
java·github·eventbus
小苏兮5 小时前
【C++】类与对象(下)
开发语言·c++·学习
咖啡Beans5 小时前
异步处理是企业开发的‘生存之道’!Java8和Spring的异步实现,你必须搞清楚!
java·后端