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)、优化程序性能(例如延迟加载)以及实现一些高级功能(例如热部署)
相关推荐
橘猫云计算机设计4 分钟前
基于springboot微信小程序的旅游攻略系统(源码+lw+部署文档+讲解),源码可白嫖!
java·spring boot·后端·微信小程序·毕业设计·旅游
落榜程序员5 分钟前
Java 基础-30-单例设计模式:懒汉式与饿汉式
java·开发语言
顾林海5 分钟前
深度解析ArrayList工作原理
android·java·面试
雷渊7 分钟前
spring-IoC容器启动流程源码分析
java·后端·面试
用户33154891110712 分钟前
一招搞定Java线程池炸弹,系统吞吐量暴增10倍!
java·后端
努力的搬砖人.16 分钟前
maven如何使用
java·后端·面试·maven
风象南20 分钟前
SpringBoot中6种跨域请求解决方案
java·spring boot·后端
vivo互联网技术22 分钟前
活动中台系统慢 SQL 治理实践
java·数据库·后端
是小李呀~32 分钟前
【工作梳理】怎么把f12里面的东西导入到postman
java
攀小黑33 分钟前
Java 多线程加锁 synchronized 关键字 字符串当做key
java·开发语言