JVM类的加载

一、如何定义类的初始化段

类的初始化段(也称为静态初始化块 )是用于在类加载时执行初始化逻辑的代码块,定义方式是在代码块前加 static 关键字。它属于类级别的初始化,只在类被加载并初始化时执行一次,用于初始化静态变量或执行类级别的预处理逻辑。

语法示例:

java 复制代码
public class MyClass {
    // 静态变量
    private static int count;

    // 静态初始化块(类的初始化段)
    static {
        System.out.println("类初始化段执行");
        count = 10; // 初始化静态变量
    }

    // 普通成员变量
    private int num;

    // 普通初始化块(实例初始化段,不属于类初始化)
    {
        System.out.println("实例初始化段执行");
        num = 20; // 初始化实例变量
    }

    public static void main(String[] args) {
        System.out.println("main方法执行");
        System.out.println("count = " + count); // 访问静态变量
    }
}

说明:

  • 静态初始化块(类初始化段) :由 static {} 定义,属于类本身,在类加载并初始化时执行,且只执行一次(无论创建多少对象)。
  • 普通初始化块 :由 {} 定义,属于实例,每次创建对象时会在构造方法之前执行,用于初始化实例变量(非静态)。

二、类的加载、初始化与 main 方法的执行顺序

是的,类会先完成加载和初始化,然后才执行 main 方法。具体流程如下:

  1. 类加载 :JVM 首次遇到某个类时(如执行 main 方法所在的类),会将其字节码加载到内存中(类加载过程)。
  2. 类初始化 :加载完成后,JVM 会执行类的初始化逻辑,包括:
    • 静态变量的声明与赋值(如 private static int count = 5;)。
    • 静态初始化块(类的初始化段)中的代码,按定义顺序执行。
  3. 执行 main 方法 :类初始化完成后,JVM 才会调用该类的 static main 方法,作为程序入口。

验证示例

运行上面的 MyClass,输出顺序为:

plaintext

复制代码
类初始化段执行  // 类初始化时执行
main方法执行     // 类初始化完成后,执行main方法
count = 10

如果在 main 方法中创建对象:

java

运行

复制代码
public static void main(String[] args) {
    System.out.println("main方法执行");
    MyClass obj = new MyClass(); // 创建对象
}

输出顺序为:

plaintext

复制代码
类初始化段执行  // 类先初始化
main方法执行     // 执行main
实例初始化段执行 // 创建对象时,执行实例初始化块(在构造方法前)

总结

  1. 类的初始化段(静态初始化块)通过 static {} 定义,用于类加载时的初始化,只执行一次。
  2. 程序启动时,JVM 会先加载并初始化包含 main 方法的类(执行静态变量赋值和静态初始化块),完成后才会调用 main 方法
  3. 实例初始化块(普通初始化块)属于对象,在每次创建对象时执行,与类的加载 / 初始化阶段无关。

因为这种机制,包含main方法的类可以在main方法中直接创建本类的实例,即使创建实例后边有其他代码也无所谓,因为java是先加载完类的所有编译完的字节码放到内存中,在实例化的时候已经知道类的所有代码,而不是像python读一行操作一行

1. Java 是 "预编译 + 类加载" 机制,而非 "逐行解释执行"

Java 代码需要先编译成字节码(.class 文件),再由 JVM 加载到内存中执行。在类加载阶段,JVM 会完整解析类的所有结构 (包括类名、父类、方法、字段、构造器等),并将这些信息存入方法区(元空间)。这意味着:当执行 main 方法时,JVM 已经 "知道" 这个类的所有定义(包括自身的构造器、方法等),无论实例化代码写在类的哪个位置(即使在 main 方法中间,甚至在类的后半部分定义构造器),JVM 都能找到对应的结构并创建实例。

例如,即使代码写成这样,也能正常运行:

java 复制代码
public class Test {
    public static void main(String[] args) {
        Test t = new Test(); // 这里创建实例时,JVM 已经知道 Test 类的所有定义
        t.print();
    }

    // 方法定义在 main 之后,但不影响调用
    public void print() {
        System.out.println("Hello");
    }
}

2. Python 是 "逐行解释执行",依赖 "提前声明"

Python 是解释型语言,没有预编译阶段,代码从上到下逐行执行。如果在定义类的实例或调用方法之前,解释器还没 "读到" 类的完整定义,就会报错。例如,下面的 Python 代码会报错(因为创建实例时,Test 类还未定义完成):

java 复制代码
t = Test()  # 报错:NameError: name 'Test' is not defined

class Test:
    def print(self):
        print("Hello")

必须先定义类,再创建实例,这是由逐行解释的机制决定的。

总结

Java 之所以允许在 main 方法中直接创建本类实例(无论代码顺序如何),核心原因是:类在执行 main 方法之前,已经通过编译和加载过程被完整解析到内存中,JVM 对类的所有结构(包括自身)已有完整认知,而不像 Python 那样依赖代码的逐行执行顺序。这种机制保证了 Java 中类的 "自引用"(如在自身方法中创建实例)不会因代码位置而出现问题。

相关推荐
JAVA学习通6 小时前
JDK高版本特性总结与ZGC实践
java·jvm·算法
只想码代码8 小时前
什么是程序计数器?
java·jvm
JAVA学习通8 小时前
OJ竞赛平台----C端题目列表
java·开发语言·jvm·vue.js·elasticsearch
m0_475064501 天前
jvm中的栈
jvm
我有一颗五叶草1 天前
JVM - 内存泄露与内存溢出
jvm
周杰伦_Jay2 天前
【Java虚拟机(JVM)全面解析】从原理到面试实战、JVM故障处理、类加载、内存区域、垃圾回收
java·jvm
星梦清河2 天前
宋红康 JVM 笔记 Day18|class文件结构
jvm
晓风残月淡2 天前
JVM字节码与类的加载(二):类加载器
jvm·python·php
用手手打人2 天前
JVM(十)-- 类的加载器
jvm