类变量生命周期始于初始化阶段执行后赋值,终于类卸载;其存续依赖class对象是否被jvm持有,而classloader仅在首次主动使用(如new、调用static方法等)时触发加载与初始化。

Java 类变量的生命周期和 ClassLoader 的加载时机紧密关联,但二者不是同一概念——类变量存续依赖于类本身的生命周期,而类何时被加载,取决于 ClassLoader 的行为和 JVM 的触发规则。
类变量的生命周期从初始化开始,到类卸载结束
类变量(即 static 修饰的字段)属于类级别,不随对象创建而重复分配。它的存在周期与对应 Class 对象的生命周期一致:
-
起点:不是声明时,也不是准备阶段赋零值时,而是初始化阶段执行
<clinit></clinit>方法后,静态变量才真正获得代码中指定的值; -
存续期:只要该类的
Class对象仍被 JVM 持有(比如被类加载器引用、被其他活跃对象引用),类变量就持续有效; -
终点:当满足以下全部条件时,类可能被卸载,类变量随之消失:
- 该类所有实例已被垃圾回收;
- 加载该类的
ClassLoader实例本身已被 GC; -
Class对象没有被任何地方强引用(如静态引用、线程栈、JNI 引用等)。
注意:普通应用中,AppClassLoader 加载的类几乎不会被卸载;只有在 OSGi、Spring Boot DevTools、或自定义 ClassLoader 配合弱引用/动态部署场景下,才可能触发类卸载。
ClassLoader 加载类的时机决定类变量何时“可用”
类加载不是在编译时发生,而是在运行时按需触发。ClassLoader 只在首次主动使用该类时启动加载流程(包括后续的验证、准备、解析、初始化):
-
触发加载的典型动作包括:
-
new创建该类实例; - 访问或赋值其
static字段(读写都算,但仅读final static常量不算主动使用); - 调用其
static方法; - 使用
Class.forName("xxx")或ClassLoader.loadClass("xxx")(后者不触发初始化,前者默认触发); - 初始化子类时,若父类未初始化,则先触发父类加载与初始化。
-
-
关键细节:
-
ClassLoader.loadClass()仅完成加载+链接(到解析为止),不触发初始化,因此此时类变量还停留在“准备阶段”的零值或常量值,<clinit></clinit>未执行; -
Class.forName(String)默认调用forName(String, true, ClassLoader),第二个参数为true,表示触发初始化,类变量才会被赋予代码中写的初始值; -
static final基本类型常量(如static final int PORT = 8080;)在准备阶段就直接赋值,不依赖<clinit></clinit>,所以即使类未初始化也能访问其值(编译期常量,可能被内联)。
-
类变量初始化顺序受 <clinit></clinit> 执行逻辑约束
JVM 会将类中所有静态变量赋值语句和静态代码块按源码顺序合并进 <clinit></clinit> 方法,并保证:
- 同一类中,静态变量初始化严格按声明顺序执行;
- 父类
<clinit></clinit>总在子类之前执行(若子类首次使用触发初始化); -
<clinit></clinit>是线程安全的:JVM 通过隐式锁确保它最多只被执行一次。
例如:
class Parent {
static { System.out.println("Parent clinit"); }
}
class Child extends Parent {
static { System.out.println("Child clinit"); }
}
首次访问 Child 时,输出顺序一定是:
Parent clinit Child clinit
类变量的生命 span 和 ClassLoader 的加载节奏共同构成 Java 静态上下文的稳定性基础。理解它们的协同机制,对排查静态资源泄漏、热部署失败、类初始化死锁等问题至关重要。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











