类加载与初始化时机详解

问题描述

在Java中,类型会在什么时间、什么条件下由JVM加载?加载后一定会初始化吗?

详细解答

核心概念

类加载≠类初始化

  • 类加载:将.class文件读入内存,创建Class对象
  • 类初始化:执行类的静态初始化代码,包括静态变量赋值和静态代码块

类的生命周期

类的完整生命周期包括7个阶段:

  1. 加载(Loading)
  2. 验证(Verification)
  3. 准备(Preparation)
  4. 解析(Resolution)
  5. 初始化(Initialization)
  6. 使用(Using)
  7. 卸载(Unloading)

1. 类加载时机演示

public class ClassLoadingDemo {

    static {
        System.out.println("ClassLoadingDemo的静态代码块执行");
    }

    // 测试类:观察加载时机
    static class TestClass {
        static {
            System.out.println("TestClass被初始化");
        }

        public static final String CONSTANT = "常量";
        public static String staticField = "静态字段";

        public static void staticMethod() {
            System.out.println("TestClass的静态方法");
        }
    }

    // 子类:观察继承中的加载
    static class ChildClass extends TestClass {
        static {
            System.out.println("ChildClass被初始化");
        }

        public static String childField = "子类静态字段";
    }

    public static void main(String[] args) throws ClassNotFoundException {
        System.out.println("=== 主方法开始 ===");

        // 1. 访问编译时常量 - 不会触发类初始化
        System.out.println("访问常量: " + TestClass.CONSTANT);

        System.out.println("\n=== 触发类初始化的操作 ===");

        // 2. 访问静态字段 - 触发类初始化
        System.out.println("访问静态字段: " + TestClass.staticField);

        // 3. 调用静态方法 - 也会触发(如果类未初始化)
        TestClass.staticMethod();

        // 4. 创建实例 - 触发类初始化
        TestClass instance = new TestClass();

        // 5. 访问子类字段 - 先初始化父类,再初始化子类
        System.out.println("\n访问子类字段:");
        System.out.println(ChildClass.childField);

        // 6. Class.forName() - 触发类初始化
        System.out.println("\n使用Class.forName:");
        Class.forName("java.util.ArrayList");

        demonstrateClassLoaderBehavior();
    }

    public static void demonstrateClassLoaderBehavior() {
        System.out.println("\n=== ClassLoader行为演示 ===");

        try {
            // 只加载不初始化
            ClassLoader loader = ClassLoadingDemo.class.getClassLoader();
            Class<?> clazz = loader.loadClass("java.lang.String");
            System.out.println("加载了String类: " + clazz.getName());

            // Class.forName默认会初始化
            Class<?> clazz2 = Class.forName("java.util.HashMap");
            System.out.println("加载并初始化HashMap类: " + clazz2.getName());

            // Class.forName不初始化
            Class<?> clazz3 = Class.forName("java.util.TreeMap", false, loader);
            System.out.println("只加载TreeMap类: " + clazz3.getName());

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

2. 类初始化触发条件

public class InitializationTriggers {

    // 父类
    static class Parent {
        static {
            System.out.println("Parent类初始化");
        }

        public static String parentField = getParentField();

        private static String getParentField() {
            System.out.println("Parent静态字段初始化");
            return "父类字段";
        }
    }

    // 子类
    static class Child extends Parent {
        static {
            System.out.println("Child类初始化");
        }

        public static String childField = getChildField();

        private static String getChildField() {
            System.out.println("Child静态字段初始化");
            return "子类字段";
        }
    }

    // 包含常量的类
    static class Constants {
        static {
            System.out.println("Constants类初始化");
        }

        public static final String COMPILE_TIME_CONSTANT = "编译时常量";
        public static final String RUNTIME_CONSTANT = getRuntimeConstant();

        private static String getRuntimeConstant() {
            System.out.println("运行时常量初始化");
            return "运行时常量";
        }
    }

    public static void main(String[] args) throws Exception {
        System.out.println("=== 类初始化触发条件测试 ===");

        // 1. 访问编译时常量 - 不触发初始化
        System.out.println("1. 访问编译时常量:");
        System.out.println(Constants.COMPILE_TIME_CONSTANT);

        // 2. 访问运行时常量 - 触发初始化
        System.out.println("\n2. 访问运行时常量:");
        System.out.println(Constants.RUNTIME_CONSTANT);

        // 3. 通过子类访问父类静态字段 - 只初始化父类
        System.out.println("\n3. 通过子类访问父类字段:");
        System.out.println(Child.parentField);

        // 4. 访问子类静态字段 - 先初始化父类,再初始化子类
        System.out.println("\n4. 访问子类字段:");
        System.out.println(Child.childField);

        // 5. 数组类型 - 不触发组件类型初始化
        System.out.println("\n5. 创建数组:");
        Parent[] array = new Parent[10];
        System.out.println("创建了Parent数组,长度: " + array.length);

        // 6. 反射获取Class对象 - 不触发初始化
        System.out.println("\n6. 反射获取Class对象:");
        Class<?> clazz = Parent.class;
        System.out.println("获取了Class对象: " + clazz.getName());

        demonstrateDetailedBehavior();
    }

    public static void demonstrateDetailedBehavior() throws Exception {
        System.out.println("\n=== 详细行为演示 ===");

        // 创建新的测试类
        class LazyClass {
            static {
                System.out.println("LazyClass被初始化");
            }

            public static void doSomething() {
                System.out.println("LazyClass.doSomething()");
            }
        }

        // Class.forName with initialization = false
        System.out.println("使用Class.forName(name, false, loader):");
        Class<?> clazz = Class.forName(
            LazyClass.class.getName(), 
            false, 
            Thread.currentThread().getContextClassLoader()
        );
        System.out.println("类已加载但未初始化");

        // 手动初始化
        System.out.println("\n手动触发初始化:");
        LazyClass.doSomething();  // 触发初始化
    }
}

类加载过程详解

1. 加载、验证、准备阶段

public class ClassLoadingProcess {

    static class LoadingExample {
        // 在准备阶段,这些字段会被赋予默认值
        public static boolean boolField;        // false
        public static int intField;             // 0
        public static String stringField;       // null

        // final常量在准备阶段就会被赋予正确的值
        public static final int FINAL_INT = 100;
        public static final String FINAL_STRING = "常量字符串";

        // 非final的静态变量在初始化阶段才会执行赋值
        public static int calculatedField = calculateValue();

        private static int calculateValue() {
            System.out.println("计算静态字段值");
            return 42;
        }

        static {
            System.out.println("LoadingExample静态代码块执行");
            System.out.println("此时boolField = " + boolField);
            System.out.println("此时intField = " + intField);
            System.out.println("此时stringField = " + stringField);
        }
    }

    public static void main(String[] args) {
        System.out.println("=== 类加载过程演示 ===");

        // 访问final常量 - 不会触发类初始化
        System.out.println("访问final常量: " + LoadingExample.FINAL_INT);
        System.out.println("访问final字符串: " + LoadingExample.FINAL_STRING);

        // 访问非final静态字段 - 触发类初始化
        System.out.println("\n访问非final静态字段:");
        System.out.println("calculatedField = " + LoadingExample.calculatedField);

        demonstrateClassState();
    }

    public static void demonstrateClassState() {
        System.out.println("\n=== 类状态演示 ===");

        // 获取类的状态信息
        Class<LoadingExample> clazz = LoadingExample.class;
        System.out.println("类名: " + clazz.getName());
        System.out.println("类加载器: " + clazz.getClassLoader());
        System.out.println("是否接口: " + clazz.isInterface());
        System.out.println("是否数组: " + clazz.isArray());
        System.out.println("是否枚举: " + clazz.isEnum());

        // 字段信息
        System.out.println("\n字段信息:");
        java.lang.reflect.Field[] fields = clazz.getDeclaredFields();
        for (java.lang.reflect.Field field : fields) {
            System.out.println("  " + field.getName() + " - " + field.getType().getSimpleName());
        }
    }
}

2. 类加载器层次结构

public class ClassLoaderHierarchy {

    static class CustomClass {
        static {
            System.out.println("CustomClass被加载器加载: " + 
                             CustomClass.class.getClassLoader());
        }
    }

    public static void main(String[] args) {
        System.out.println("=== 类加载器层次结构 ===");

        // 当前类的类加载器
        ClassLoader currentLoader = ClassLoaderHierarchy.class.getClassLoader();
        System.out.println("当前类加载器: " + currentLoader);

        // 父类加载器链
        ClassLoader parent = currentLoader.getParent();
        System.out.println("父类加载器: " + parent);

        if (parent != null) {
            ClassLoader grandParent = parent.getParent();
            System.out.println("祖父类加载器: " + grandParent);
        }

        // 系统类的加载器
        System.out.println("\n=== 系统类的加载器 ===");
        System.out.println("String类加载器: " + String.class.getClassLoader());
        System.out.println("ArrayList类加载器: " + java.util.ArrayList.class.getClassLoader());

        // 触发自定义类加载
        System.out.println("\n=== 触发自定义类加载 ===");
        new CustomClass();

        demonstrateClassLoaderBehavior();
    }

    public static void demonstrateClassLoaderBehavior() {
        System.out.println("\n=== 类加载器行为演示 ===");

        try {
            // 不同的类加载方式
            ClassLoader loader = Thread.currentThread().getContextClassLoader();

            // 方式1: Class.forName() - 默认初始化
            System.out.println("1. Class.forName(默认初始化):");
            Class<?> clazz1 = Class.forName("java.util.LinkedList");

            // 方式2: Class.forName() - 不初始化
            System.out.println("2. Class.forName(不初始化):");
            Class<?> clazz2 = Class.forName("java.util.TreeSet", false, loader);

            // 方式3: ClassLoader.loadClass() - 不初始化
            System.out.println("3. ClassLoader.loadClass():");
            Class<?> clazz3 = loader.loadClass("java.util.HashSet");

            System.out.println("所有类都已加载完成");

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

类初始化的线程安全

1. 多线程环境下的类初始化

import java.util.concurrent.CountDownLatch;

public class ThreadSafeInitialization {

    static class SlowInitClass {
        static {
            System.out.println("SlowInitClass开始初始化,线程: " + 
                             Thread.currentThread().getName());

            try {
                // 模拟耗时的初始化过程
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }

            System.out.println("SlowInitClass初始化完成,线程: " + 
                             Thread.currentThread().getName());
        }

        public static void doSomething() {
            System.out.println("SlowInitClass.doSomething(),线程: " + 
                             Thread.currentThread().getName());
        }
    }

    public static void main(String[] args) throws InterruptedException {
        System.out.println("=== 多线程类初始化演示 ===");

        int threadCount = 3;
        CountDownLatch startLatch = new CountDownLatch(1);
        CountDownLatch finishLatch = new CountDownLatch(threadCount);

        // 创建多个线程同时触发类初始化
        for (int i = 0; i < threadCount; i++) {
            final int threadNum = i;
            Thread thread = new Thread(() -> {
                try {
                    startLatch.await();  // 等待信号,确保所有线程同时开始

                    System.out.println("线程" + threadNum + "尝试访问SlowInitClass");
                    SlowInitClass.doSomething();

                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                } finally {
                    finishLatch.countDown();
                }
            }, "Thread-" + threadNum);

            thread.start();
        }

        // 让所有线程同时开始
        System.out.println("启动所有线程...");
        startLatch.countDown();

        // 等待所有线程完成
        finishLatch.await();
        System.out.println("所有线程完成");

        demonstrateDeadlockPrevention();
    }

    public static void demonstrateDeadlockPrevention() {
        System.out.println("\n=== JVM防止初始化死锁演示 ===");

        // JVM会防止循环依赖导致的死锁
        class ClassA {
            static {
                System.out.println("ClassA开始初始化");
                // 这里不会造成死锁,因为JVM有特殊处理
                System.out.println("ClassA初始化完成");
            }
        }

        class ClassB {
            static {
                System.out.println("ClassB开始初始化");
                // 访问ClassA
                new ClassA();
                System.out.println("ClassB初始化完成");
            }
        }

        new ClassB();
    }
}

类卸载机制

1. 类卸载条件

import java.lang.ref.WeakReference;

public class ClassUnloadingDemo {

    public static void main(String[] args) throws Exception {
        System.out.println("=== 类卸载演示 ===");

        demonstrateClassUnloading();
        demonstrateCustomClassLoader();
    }

    public static void demonstrateClassUnloading() {
        System.out.println("1. 类卸载的条件:");
        System.out.println("   - 该类的所有实例都被回收");
        System.out.println("   - 加载该类的ClassLoader被回收");
        System.out.println("   - 该类的java.lang.Class对象没有被引用");

        // 大多数情况下,由应用类加载器加载的类不会被卸载
        // 因为应用类加载器通常不会被回收
    }

    public static void demonstrateCustomClassLoader() throws Exception {
        System.out.println("\n2. 自定义类加载器的类可能被卸载:");

        // 创建自定义类加载器
        ClassLoader customLoader = new ClassLoader() {
            @Override
            protected Class<?> findClass(String name) throws ClassNotFoundException {
                // 这里通常会从文件或网络加载类字节码
                // 为了演示,我们返回一个简单的类
                if ("CustomLoadedClass".equals(name)) {
                    byte[] classBytes = generateSimpleClassBytes();
                    return defineClass(name, classBytes, 0, classBytes.length);
                }
                return super.findClass(name);
            }
        };

        // 加载类
        Class<?> clazz = customLoader.loadClass("CustomLoadedClass");
        WeakReference<Class<?>> classRef = new WeakReference<>(clazz);
        WeakReference<ClassLoader> loaderRef = new WeakReference<>(customLoader);

        System.out.println("自定义类已加载: " + clazz.getName());

        // 清除强引用
        clazz = null;
        customLoader = null;

        // 强制垃圾回收
        System.gc();
        Thread.sleep(1000);

        // 检查是否被回收
        System.out.println("类是否被回收: " + (classRef.get() == null));
        System.out.println("类加载器是否被回收: " + (loaderRef.get() == null));
    }

    private static byte[] generateSimpleClassBytes() {
        // 这里应该返回有效的类字节码
        // 为了演示,返回一个空数组(实际使用时需要真实的字节码)
        return new byte[0];
    }
}

实际应用场景

1. 懒加载设计模式

public class LazyLoadingPatterns {

    // 单例模式:利用类初始化的线程安全性
    static class Singleton {
        private static class InstanceHolder {
            private static final Singleton INSTANCE = new Singleton();

            static {
                System.out.println("Singleton实例被创建");
            }
        }

        public static Singleton getInstance() {
            return InstanceHolder.INSTANCE;  // 触发InstanceHolder的初始化
        }

        private Singleton() {
            System.out.println("Singleton构造器执行");
        }
    }

    // 工厂类:按需加载
    static class ResourceFactory {
        // 这些类只有在实际使用时才会被加载
        private static class DatabaseConfig {
            static {
                System.out.println("数据库配置类被加载");
            }

            static final String URL = "jdbc:mysql://localhost:3306/db";
        }

        private static class CacheConfig {
            static {
                System.out.println("缓存配置类被加载");
            }

            static final String HOST = "localhost:6379";
        }

        public static String getDatabaseUrl() {
            return DatabaseConfig.URL;  // 触发DatabaseConfig加载
        }

        public static String getCacheHost() {
            return CacheConfig.HOST;   // 触发CacheConfig加载
        }
    }

    public static void main(String[] args) {
        System.out.println("=== 懒加载模式演示 ===");

        System.out.println("1. 单例模式:");
        System.out.println("主方法开始,但Singleton还未初始化");

        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();

        System.out.println("两个实例是否相同: " + (instance1 == instance2));

        System.out.println("\n2. 工厂类按需加载:");
        System.out.println("获取数据库URL: " + ResourceFactory.getDatabaseUrl());
        System.out.println("获取缓存主机: " + ResourceFactory.getCacheHost());
    }
}

2. 性能优化应用

public class PerformanceOptimization {

    // 重型资源类:只在需要时加载
    static class HeavyResource {
        static {
            System.out.println("HeavyResource开始加载...");

            // 模拟重型资源初始化
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }

            System.out.println("HeavyResource加载完成");
        }

        public static void useResource() {
            System.out.println("使用重型资源");
        }
    }

    // 条件加载
    static class ConditionalLoading {
        public static void loadIfNeeded(boolean condition) {
            if (condition) {
                // 只有在条件为true时才触发类加载
                HeavyResource.useResource();
            } else {
                System.out.println("不需要加载重型资源");
            }
        }
    }

    public static void main(String[] args) {
        System.out.println("=== 性能优化演示 ===");

        System.out.println("程序启动,重型资源还未加载");

        // 根据条件决定是否加载
        ConditionalLoading.loadIfNeeded(false);

        System.out.println("\n现在需要使用重型资源:");
        ConditionalLoading.loadIfNeeded(true);

        measureLoadingTime();
    }

    public static void measureLoadingTime() {
        System.out.println("\n=== 加载时间测量 ===");

        class QuickClass {
            static {
                System.out.println("QuickClass快速加载");
            }
        }

        long start = System.nanoTime();
        new QuickClass();
        long end = System.nanoTime();

        System.out.printf("类加载耗时: %.2f ms%n", (end - start) / 1_000_000.0);
    }
}

总结

类加载与初始化的关键点

1. 类加载时机

  • 主动引用:创建实例、访问静态字段/方法、反射、子类触发父类
  • 被动引用:访问编译时常量、数组类型、Class.forName(name, false, loader)

2. 加载≠初始化

  • 加载:读取.class文件,创建Class对象(验证、准备、解析)
  • 初始化:执行静态代码块和静态字段赋值

3. 初始化顺序

  1. 父类静态代码块和静态字段
  2. 子类静态代码块和静态字段
  3. 父类实例代码块和构造器
  4. 子类实例代码块和构造器

4. 线程安全

  • JVM保证类初始化的线程安全
  • 只有一个线程执行初始化,其他线程等待

5. 实际应用

  • 单例模式的延迟初始化
  • 按需加载重型资源
  • 避免不必要的类加载开销
powered by Gitbook© 2025 编外计划 | 最后修改: 2025-07-28 18:05:38

results matching ""

    No results matching ""