类的生命周期


一个.class文件从被加载到JVM内存,到被卸载,一共经历了7个阶段:加载 → 验证 → 准备 → 解析 → 初始化 → 使用 → 卸载,其中验证、准备、解析统称为链接。

加载

  1. 通过类的全限定名来获取定义类的二进制字节流(来源包括.class文件、Zip/Jar包、网络传输、动态代理运行时生成等)。
  2. 将字节流中的静态存储结构转化为方法区的运行时数据结构。
  3. 在堆内存中生成一个代表该类的java.lang.Class对象,作为程序访问该类方法区中数据的外部入口。

验证

保证字节流对应的Class文件的信息符合《Java虚拟机规范》,不会危害虚拟机安全。
其中包含:

  • 文件格式验证:验证二进制字节流符合Class文件格式规范,能够被当前版本JVM处理。
    • 魔数检查:文件的前4个字节必须是0xCAFEBABE。
    • 版本号检查:主次版本号是否在当前JVM能够处理的范围内,不能会抛UnsupportedClassVersionError。
    • 常量池检查:常量池中的常量类型是否存在不支持的类型标签,索引是否指向了不存在的常量或不合法的常量。
    • UTF-8编码检查:CONSTANT_Utf8_info的常量中是否存在不符合UTF-8编码的数据。
  • 元数据验证:验证类的元数据(语法结构和语义)是否符合Java语言规范。
    • 继承合法性:该类是否有父类(除java.lang.Object外都有父类)。
    • Final约束:该类是否继承了被final修饰的类。
    • 抽象类约束:如果该类不是抽象类,是否实现了父类或接口中要求实现的所有方法。
    • 重写/重载合法性:类中的字段、方法是否与父类产生矛盾。
  • 字节码验证:通过数据流和控制流分析,确定程序语义是合法的。
    • 类型安全:保证操作数栈的数据类型与字节码指令正常配合工作。
    • 跳转安全:保证方法中的控制流跳转指令(如goto)不会跳转到方法体之外的字节码指令上。
    • 类型转换安全:保证方法体中的类型转换是有效的。
  • 符号引用验证:发生在解析阶段将符号引用转化为直接引用时,验证类是否缺少或无权访问其引用的外部类、字段或方法。
    • 类存在性:符号引用中通过全限定名能否找到对应类。
    • 成员存在性:指定类中是否存在符号引用描述和的字段或方法。
    • 访问权限:符号引用中的类、字段、方法的可访问性是否允许当前类进行访问。

准备

为类的静态变量分配内存并设置初始值(零值)。
对于final修饰的基本数据类型/String常量,在编译时javac会为该变量生成ConstantValue放入常量池,准备阶段直接赋值为常量值。
其他静态变量设置初始值,如0、false、null等。

解析

将Class文件常量池中的符号引用替换为直接引用。

  • 符号引用:用一组符号来描述目标,不依赖内存布局。
  • 直接引用:直接指向目标的内存指针、相对偏移量或间接能够定位到目标的句柄,与JVM内存布局相关。
    解析的对象包括类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符等。
    解析分为静态解析和动态解析(延迟解析):
  • 静态解析:在链接阶段就将常量池所有符号引用一次性解析完毕。
  • 动态解析/延迟解析:真正使用该符号引用时才去解析该符号(如getstatic等)。

初始化

执行类构造器<clinit>()方法的过程。
<clinit>():由编译器自动收集类中所有类变量的赋值动作和静态代码块(static{}) ,按源代码顺序合并生成<clinit>()方法。
特点:

  • 执行顺序:父类的<clinit>()先于子类<clinit>()执行。接口的<clinit>()执行时不需要先执行父接口的<clinit>()。
  • 线程安全:JVM会保证一个类的<clinit>()方法在多线程环境中被正确地加锁同步。

初始化的时机

  1. 字节码指令触发:遇到new,getstatic,putstatic、invokestatic指令时。
  2. 反射调用:使用java.lang.reflect包的方法对类进行反射调用时,如Class.forName()。
  3. 父类初始化:初始化子类时,如果父类还没初始化,先触发父类的初始化。
  4. 启动主类:被指定的执行主类(包含main())会被初始化。
  5. 动态语言支持(JDK 7+):使用java.lang.invoke.MethodHandle实例解析出的方法句柄对应的类未初始化时。
  6. 接口默认方法:初始化一个接口的实现类时,如果接口定义了默认方法,会先触发接口的初始化。

不会初始化的场景

  1. 通过子类引用父类的静态字段:只会触发父类的初始化,不会触发子类的初始化。
System.out.println(SubClass.superStaticField); // 只初始化 SuperClass
  1. 通过数组引用来定义类:数组类是独立的类,对应的类不会初始化。
User[] users = new User[10]; // User 类不会被初始化
  1. 编译期常量:编译期会将常量存入调用类的常量池,调用时不会引用定义常量的类。
public class MyClass {
	public static final String HELLO = "hello world";
	// ...
}
System.out.println(MyClass.HELLO); // MyClass 不会被初始化

类加载器


类加载器负责将.class文件加载到JVM内存中,生成对应的Class对象。

层次结构

flowchart BT
    Bootstrap["Bootstrap ClassLoader"]
    Ext["Extension ClassLoader"]
    App["Application ClassLoader"]
    Custom["Custom ClassLoader"]
    
    Custom --> App
    App --> Ext
    Ext --> Bootstrap

Bootstrap ClassLoader(启动类加载器)

由C++实现,属于虚拟机的一部分,在Java代码中获取其引用会返回null。
加载<JAVA_HOME>/lib目录下或-Xbootclasspath指定的路径下的jar包(如rt.jar、tools.jar等)。
无父加载器。

Extension ClassLoader(扩展类加载器)

由Java实现,位于sun.misc.Launcher$ExtClassLoader。
加载<JAVA_HOME>/lib/ext目录下或系统变量java.ext.dirs指定的路径下的jar包。
父加载器为Bootstrap ClassLoader。

JDK9模块化改动

ExtClassLoader被重命名为Platform ClassLoader,取消了 lib/ext 目录,负责加载模块化平台 API。

Application ClassLoader(应用程序类加载器)

由Java实现,位于sun.misc.Launcher$AppClassLoader,是程序默认的类加载器(ClassLoader.getSystemClassLoader())。
加载用户类路径classpath下的类。
父加载器为Extension ClassLoader。

Custom ClassLoader(自定义类加载器)

用户编写的继承自ClassLoader的类加载器。
父加载器为Application ClassLoader。
相关方法:

protected Class loadClass(String name, boolean resolve) // 重写可打破双亲委派
protected Class findClass(String name) // 重写遵循双亲委派

双亲委派模型

JDK在1.2引入了双亲委派模型。

原理

一个类加载器的加载过程:

  1. 自底向上检查:当收到类加载请求时,先检查自己的缓存中是否加载过:
    1. 若有则直接返回对应的Class对象
    2. 若自己没有加载过,则将请求委派给父加载器,直到Bootstrap ClassLoader。
  2. 自顶向下加载:当父加载器无法完成加载时,自己才会尝试加载类。
protected Class<?> loadClass(String name, boolean resolve)  
    throws ClassNotFoundException  
{  
    synchronized (getClassLoadingLock(name)) {  
        // 检查类是否已经被加载过
        Class<?> c = findLoadedClass(name);  
        if (c == null) {  // 没加载过
            long t0 = System.nanoTime();  
            try {  
                if (parent != null) { // 委派给父加载器 
                    c = parent.loadClass(name, false);  
                } else {  // 父加载器为null说明是Bootstrap ClassLoader
                    c = findBootstrapClassOrNull(name);  
                }  
            } catch (ClassNotFoundException e) {  
	            // 父类加载器抛出ClassNotFoundException表示父加载器加载范围内找不到该类
			}  
  
            if (c == null) {  
				// 父加载器加载失败后,调用本加载器的findClass尝试加载
                c = findClass(name);  
  
                // this is the defining class loader; record the stats  
                PerfCounter.getParentDelegationTime().addTime(t1 - t0);  
                PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);  
                PerfCounter.getFindClasses().increment();  
            }  
        }  
        if (resolve) {  
            resolveClass(c);  // 解析类
        }  
        return c;  
    }  
}

原因

  1. 安全性:防止核心类被篡改。如果用户写了java.lang.String等类,最终委派给Bootstrap ClassLoader加载,Bootstrap ClassLoader会保证加载的是JDK自带的类。
  2. 避免重复加载:只有类全限定名相同和由同一个加载器加载的类才是相同类。防止不同加载器加载同一个类,导致内存中出现多个Class对象。

JVM核心类的安全

  1. 双亲委派模型保证了核心类被Bootstrap ClassLoader加载,防止被篡改。
  2. 验证阶段ClassLoader.defineClass()会校验包名等是否与核心类相同,非法的会抛出SecurityException。

打破双亲委派模型

重写loadClass()方法

将loadClass()中先将加载请求委派给父加载器的部分去掉。

线程上下文类加载器(TCCL)

Thread.currentThread().getContextClassLoader()可以获取当前线程上下文的类加载器(默认为Application ClassLoader),父加载器可以将加载请求反向委派给子加载器。
JDBC驱动等核心类接口由Bootstrap ClassLoader加载,但驱动实现类在项目的 ClassPath 下的第三方jar包中,Bootstrap ClassLoader无法加载这些类,便会委派给子加载器加载。
ServiceLoader<Driver> loader = ServiceLoader.load(Driver.class);中ServiceLoader.load用的就是TCCL。

自定义加载器和模块化框架

Tomcat为每个容器创建一个WebAppClassLoader,对于用户编写的类优先自己加载。

  1. 应用隔离:不同Web应用可能使用不同版本的相同类,需要进行隔离,因此每个应用使用独立的WebAppClassLoader,优先加载本应用 /WEB-INF/classes 和 /WEB-INF/lib 下的类。
  2. 热部署:重新部署应用时,只需要丢弃该应用的WebAppClassLoader,重新创建WebAppClassLoader并加载即可实现热部署。
  3. 容器安全:Tomcat的容器实现类由CatalinaClassLoader加载,对Web应用隔离,防止Tomcat被修改。
  4. 资源共享:通过Common ClassLoader和Shared ClassLoader,统一加载多个Web应用共享的类,节省内存。
flowchart BT
    Bootstrap["Bootstrap ClassLoader"]
    Ext["Extension ClassLoader"]
    App["Application ClassLoader"]
    Common["Common ClassLoader (Tomcat 基础公共类)"]
    Catalina["Catalina ClassLoader (Tomcat 容器内部私有类)"]
    Shared["Shared ClassLoader (WebApp 共享类)"]
    WebApp["WebApp ClassLoader (应用私有类,优先自身查找,每个Web应用一个)"]
    
    WebApp --> Shared
    Shared --> Common
    Catalina --> Common
    Common --> App
    App --> Ext
    Ext --> Bootstrap