类加载与初始化时机详解
问题描述
在Java中,类型会在什么时间、什么条件下由JVM加载?加载后一定会初始化吗?
详细解答
核心概念
类加载≠类初始化:
- 类加载:将.class文件读入内存,创建Class对象
- 类初始化:执行类的静态初始化代码,包括静态变量赋值和静态代码块
类的生命周期
类的完整生命周期包括7个阶段:
- 加载(Loading)
- 验证(Verification)
- 准备(Preparation)
- 解析(Resolution)
- 初始化(Initialization)
- 使用(Using)
- 卸载(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. 初始化顺序
- 父类静态代码块和静态字段
- 子类静态代码块和静态字段
- 父类实例代码块和构造器
- 子类实例代码块和构造器
4. 线程安全
- JVM保证类初始化的线程安全
- 只有一个线程执行初始化,其他线程等待
5. 实际应用
- 单例模式的延迟初始化
- 按需加载重型资源
- 避免不必要的类加载开销