某大厂一面:Java类的加载顺序?

Java 类的加载过程是由 类加载器ClassLoader)控制的,涉及从类路径(或 JAR 文件)中加载类的字节码到 JVM 中。类加载不仅仅是简单的读取 .class 文件,还包括类的验证、准备、初始化等多个阶段。

下面将详细介绍 Java 类的加载顺序、过程和相关机制。

1. Java 类的加载过程

Java 类加载的过程大致分为以下几个步骤:

  1. 加载(Loading)
  2. 验证(Verification)
  3. 准备(Preparation)
  4. 解析(Resolution)
  5. 初始化(Initialization)
1. 加载(Loading)

加载阶段是指通过类加载器(ClassLoader)将类的字节码从文件系统或网络加载到 JVM 的内存中,并生成 Class 对象。

  • 类加载器:负责在运行时根据类的全限定名(包括包名)将字节码文件加载到 JVM 中。

    • 如果类已经加载过,JVM 不会重新加载。
    • 加载的字节码文件存储在磁盘上(如 classes 目录或 JAR 文件)或通过其他渠道(例如网络)传输。
  • 类加载器的工作ClassLoader 依据以下规则决定如何加载类:

    • Bootstrap ClassLoader:负责加载 JVM 核心库(rt.jar)。
    • Extension ClassLoader:加载 JDK 扩展库(ext/ 目录下的类)。
    • System ClassLoader(应用程序类加载器):加载系统类路径(CLASSPATH)中的类。
2. 验证(Verification)

类加载后,JVM 会对加载的字节码进行验证,以确保它符合 JVM 的规范,且没有受到恶意代码或错误代码的影响。

验证阶段包括:

  • 文件格式验证:确保文件是合法的 .class 文件。
  • 元数据验证:检查类的元数据,如字段、方法等,是否符合 Java 虚拟机规范。
  • 字节码验证:确保字节码中的指令是合法的,并符合 JVM 运行时要求。
3. 准备(Preparation)

在这个阶段,JVM 会为类的静态变量分配内存,并将其初始化为默认值。此时,类中的静态变量尚未赋值,仅是内存的分配。

例如:

  • int 类型的静态变量默认值为 0
  • 引用类型的静态变量默认值为 null

这个阶段仅为静态变量分配内存,并未执行任何初始化代码。

4. 解析(Resolution)

解析阶段是将符号引用转换为直接引用的过程。在类文件中,可能会有一些指向其他类、方法、字段的符号引用。在解析阶段,JVM 会将这些符号引用解析为具体的内存地址或方法指针。

解析过程包括:

  • 类解析:将类的符号引用解析为类的内存地址。
  • 字段解析:将字段的符号引用解析为实际的内存地址。
  • 方法解析:将方法的符号引用解析为实际的内存地址。
5. 初始化(Initialization)

初始化阶段是类加载过程的最后一个步骤。类的静态初始化会在此时发生,静态成员会被初始化,静态代码块会执行。

  • 静态变量赋值:此时,静态变量会被赋予编译时指定的值,或者赋予默认值(如 0null)。
  • 静态代码块执行:如果类中定义了静态初始化块(static {}),它会在类加载后、初始化之前执行。静态代码块只会执行一次。

2. Java 类加载顺序

Java 类加载的顺序涉及到类的引用、静态字段、父类的加载等。加载顺序遵循以下几个规则:

类首次引用时加载

类加载的触发通常由类的引用来决定,只有在程序首次引用该类时,JVM 才会加载类。类的引用可以通过以下方式发生:

  • 使用 new 关键字创建该类的对象。
  • 调用类的静态方法或访问类的静态字段。
  • 使用反射(如 Class.forName())加载类。
  • 使用 instanceof 运算符检查对象的类型。
  • 通过反射或动态代理创建对象。
父类优先加载

如果一个类继承了父类,父类会优先加载。例如,当创建一个子类对象时,父类的静态初始化会首先执行,然后才是子类。

class A {
    static {
        System.out.println("A initialized");
    }
}

class B extends A {
    static {
        System.out.println("B initialized");
    }
}

public class Test {
    public static void main(String[] args) {
        new B();  // 输出 A 初始化,B 初始化
    }
}

输出:

A initialized
B initialized
静态成员触发加载

当类有静态成员(如静态字段、静态方法、静态代码块)时,在第一次访问这些静态成员时,类会被加载并初始化。访问静态字段和方法会触发类加载,执行静态初始化。

class A {
    static int x = 10;  // 静态变量
}

public class Test {
    public static void main(String[] args) {
        System.out.println(A.x);  // 访问静态字段会触发 A 类的加载
    }
}
懒加载(Lazy Loading)

Java 类的加载是“懒加载”的,即类只有在第一次被需要时才会被加载。即使代码中有很多类声明,JVM 也不会在启动时加载所有的类,而是根据实际的需要加载。

父类的静态初始化

Java 类加载遵循“父类优先”的规则,父类的静态变量和静态代码块会在子类初始化之前先初始化。

  • 在子类的静态初始化执行之前,父类的静态初始化会先执行一次。
初始化的时机

类的初始化发生在以下情况之一:

  • 访问类的静态字段或调用静态方法。
  • 创建类的实例。
  • 使用 Class.forName("ClassName") 手动加载类(会触发类的初始化)。

3. Java 类加载器

Java 中的类加载器(ClassLoader)负责将类从外部资源(如文件系统、网络)加载到 JVM 中。Java 采用 双亲委派模型 来实现类加载器机制。

  • Bootstrap ClassLoader:加载核心库,如 rt.jar 和 JVM 核心类。
  • Extension ClassLoader:加载扩展库,如 JDK 的 ext 目录下的类。
  • System ClassLoader(应用程序类加载器):加载应用程序类路径下的类。

类加载器之间遵循父子关系,加载的过程是 双亲委派 机制:

  • 子加载器(如 System ClassLoader)会先请求父加载器(如 Extension ClassLoader)加载类,父加载器如果不能加载,就会由子加载器来加载。
双亲委派模型
  1. 请求由子加载器发起,子加载器委托给父加载器。
  2. 如果父加载器可以加载,则由父加载器加载。
  3. 如果父加载器无法加载,子加载器再自己加载。

4. Java 类加载的顺序总结

Java 类加载的顺序通常遵循以下规律:

  1. 类首次被引用时加载:类加载是延迟的,只有在第一次使用时,类才会被加载。
  2. 父类优先加载:子类的初始化会在父类初始化之后进行。
  3. 静态成员触发加载:访问类的静态成员(字段、方法)会触发类的加载。
  4. 懒加载:类只有在首次使用时才会加载。
  5. 类加载器的父子关系:类加载器遵循父子加载器模型,父类加载器先尝试加载,子类加载器再尝试加载。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冰糖心书房

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值