JVM 类加载器在什么情况下会加载一个类?

类加载器(ClassLoader)会在多种情况下加载一个类,主要分为两大类:主动使用被动使用。JVM 规范明确规定了类的主动使用场景,在这些场景下,类加载器会触发类的加载、链接和初始化。

1. 主动使用 (Active Use):

当 JVM 遇到以下情况时,会主动使用一个类,触发类加载器加载该类:

  • 创建类的实例:

    • 使用 new 关键字创建类的实例。
    • 通过反射创建类的实例(Class.forName(), clazz.newInstance(), constructor.newInstance())。
    java 复制代码
    MyClass obj = new MyClass(); // 主动使用 MyClass
    Class<?> clazz = Class.forName("com.example.MyClass"); // 主动使用 MyClass
    MyClass obj2 = (MyClass) clazz.newInstance(); // 主动使用 MyClass
  • 访问类的静态变量 (static field):

    • 读取或设置类的静态变量(getstaticputstatic 字节码指令),除了 static final 修饰的编译时常量。
      * 编译时常量: static final 修饰的基本类型或字符串字面量,在编译时值已确定,不会触发类的初始化(直接从常量池中获取)。
      * 运行时常量: static final修饰,但值需要在运行时才能确定,会触发类的初始化。
    java 复制代码
    int value = MyClass.staticField; // 主动使用 MyClass (读取静态变量)
    MyClass.staticField = 10;     // 主动使用 MyClass (设置静态变量)
    
    // finalString 在编译期就能确定值, 直接从常量池获取, 不会触发 MyClass 初始化
    String str = MyClass.finalString;
    
    // finalValue 需要在运行时调用方法才能确定值, 会触发 MyClass 初始化
    long time = MyClass.finalValue;
  • 调用类的静态方法 (static method):

    • 使用 invokestatic 字节码指令调用类的静态方法。
    java 复制代码
    MyClass.staticMethod(); // 主动使用 MyClass
  • 反射 (Reflection):

    • 使用 java.lang.reflect 包中的方法对类进行反射操作。
    java 复制代码
    Class<?> clazz = Class.forName("com.example.MyClass"); // 主动使用 MyClass
    Method method = clazz.getMethod("myMethod"); // 主动使用 MyClass
    Field field = clazz.getField("myField");    // 主动使用 MyClass
  • 初始化类的子类:

    • 当初始化一个类时,如果其父类还没有被初始化,则会先初始化其父类(接口除外)。
    • 注意:初始化一个类的子类, 并不会触发该类所实现接口的初始化.
    java 复制代码
    // 假设 SubClass 是 MyClass 的子类
    SubClass obj = new SubClass(); // 会先触发 MyClass 的初始化,再触发 SubClass 的初始化
  • Java 虚拟机启动时的主类:

    • 当 Java 虚拟机启动时,会先初始化包含 main 方法的主类。
    java 复制代码
    // 运行 java MyMainClass
    public class MyMainClass {
        public static void main(String[] args) {
            // ...
        }
    } // 会触发 MyMainClass 的初始化
  • 接口的初始化:

    • 接口中定义了default方法 (JDK8及以后).
    • 如果一个接口的实现类被初始化, 则该接口也会被初始化.
  • JDK 1.7+ 的动态语言支持:

    • 如果一个 java.lang.invoke.MethodHandle 实例的解析结果为 REF_getStaticREF_putStaticREF_invokeStatic 的方法句柄,并且这个方法句柄对应的类没有进行过初始化,则需要先触发其初始化。

      • 使用 invokedynamic 指令.

2. 被动使用 (Passive Use):

  • 定义:

    • 除了上述主动使用的情况外,其他引用类的方式都不会触发类的初始化,称为被动使用。
    • 被动使用不会执行类的 <clinit>() 方法(即不会执行静态变量赋值和静态代码块)。
  • 常见场景:

    • 通过子类引用父类的静态字段: 只会触发父类的初始化,不会触发子类的初始化。

      java 复制代码
      class SuperClass {
          static int value = 10;
      
          static {
              System.out.println("SuperClass initialized");
          }
      }
      
      class SubClass extends SuperClass {
          static {
              System.out.println("SubClass initialized");
          }
      }
      
      public class PassiveReference {
          public static void main(String[] args) {
              System.out.println(SubClass.value); // 只会输出 "SuperClass initialized" 和 10
          }
      }
    • 定义类类型的数组: 不会触发类的初始化。

      java 复制代码
      MyClass[] arr = new MyClass[10]; // 不会触发 MyClass 的初始化
    • 引用类的常量 (编译时常量): 不会触发类的初始化(常量在编译阶段已存入常量池)。

    java 复制代码
      class MyClass{
         public static final int CONSTANT_VALUE = 10; //编译时常量
       }
       public class Test{
          public static void main(String[] args){
            System.out.println(MyClass.CONSTANT_VALUE); //不会触发MyClass初始化
          }
       }
    • 通过类加载器加载类,但不进行初始化:

      • ClassLoader.loadClass() 方法默认只加载类,不进行初始化。
      • 要触发类的初始化,需要调用 Class.forName(className, true, classLoader),并将第二个参数设置为 true
      java 复制代码
       ClassLoader classLoader = ClassLoader.getSystemClassLoader();
       // 只加载,不初始化
      Class<?> clazz1 = classLoader.loadClass("com.example.MyClass");
      
       // 加载并初始化
       Class<?> clazz2 = Class.forName("com.example.MyClass", true, classLoader);

总结:

  • 类加载器会在遇到主动使用类的情况时加载类,包括创建实例、访问静态成员、反射、初始化子类、启动主类等。
  • 被动使用类不会触发类的初始化,例如通过子类引用父类的静态字段、定义类类型的数组、引用编译时常量等。
  • ClassLoader.loadClass() 默认只加载类,不进行初始化。要加载并初始化类,可以使用 Class.forName()
  • 了解类加载的时机我助于我们排查类加载相关的错误(例如 NoClassDefFoundErrorClassNotFoundException)、优化程序性能(例如延迟加载)以及实现一些高级功能(例如热部署)
相关推荐
Fireworkitte3 小时前
Apache POI 详解 - Java 操作 Excel/Word/PPT
java·apache·excel
weixin-a153003083163 小时前
【playwright篇】教程(十七)[html元素知识]
java·前端·html
DCTANT4 小时前
【原创】国产化适配-全量迁移MySQL数据到OpenGauss数据库
java·数据库·spring boot·mysql·opengauss
Touper.4 小时前
SpringBoot -- 自动配置原理
java·spring boot·后端
黄雪超4 小时前
JVM——函数式语法糖:如何使用Function、Stream来编写函数式程序?
java·开发语言·jvm
ThetaarSofVenice4 小时前
对象的finalization机制Test
java·开发语言·jvm
望获linux6 小时前
【实时Linux实战系列】CPU 隔离与屏蔽技术
java·linux·运维·服务器·操作系统·开源软件·嵌入式软件
JosieBook6 小时前
【Java编程动手学】使用IDEA创建第一个HelloJava程序
java·开发语言·intellij-idea
Thomas_YXQ6 小时前
Unity3D DOTS场景流式加载技术
java·开发语言·unity
summer夏1236 小时前
2025.07 做什么
java·android studio