06_Java 类初始化(Initialization)
一、概述
类初始化是 Java 类加载过程的最后一步(前两步为加载、链接),也是类从 “字节码” 到 “可执行状态” 的关键转换。其核心是执行类构造器方法<clinit>()(Class Initialization Method),完成静态变量赋值、静态代码块执行等 “激活类” 的操作。
初始化完成后,类可被正常使用(如创建实例、访问静态成员),未初始化的类无法参与程序执行。
二、核心方法:<clinit>()
1. 定义与生成
<clinit>()是编译器自动生成的类构造器方法,无需程序员手动定义。它由两部分合并而成:
- 类中所有类变量(静态变量)的赋值语句(如
static int a = 10); - 类中所有静态代码块(
static {})中的逻辑。
2. 与<init>()的区别
| 特性 | <clinit>()(类构造器) | <init>()(实例构造器) |
|---|---|---|
| 作用对象 | 类(静态成员) | 对象(实例成员) |
| 触发时机 | 类首次被主动引用时 | 对象创建(new)时 |
| 生成方式 | 编译器合并静态变量赋值 + 静态代码块 | 编译器合并实例变量赋值 + 构造代码块 + 构造函数 |
3. jclasslib 可视化分析<clinit>()
<clinit>()是编译器隐式生成的方法,无法通过源码直接看到,但可以通过jclasslib工具(字节码分析工具)直观查看其字节码指令,验证类初始化的执行逻辑。
3.1 实战:分析 OrderDemo 的<clinit>()
以OrderDemo为例,通过 jclasslib 验证<clinit>()的执行顺序:
原代码:
packagecom.dwl.ex01_类加载子系统;/** * @Description * @Version 1.0.0 * @Date 2026/2/8 3:24 * @Author Dwl */publicclassOrderDemo{// 静态变量1(先执行)staticint a =1;// 静态代码块(次之)static{ a =2;// 允许赋值未声明的静态变量(编译期允许) b =3;System.out.println("静态代码块执行:a="+ a);}// 静态变量2(最后执行,覆盖前值)staticint b =4;publicstaticvoidmain(String[] args){// 输出:a=2, b=4System.out.println("a="+ a +", b="+ b);}}jclasslib 分析步骤:

| 字节码指令 | 含义解释 | 对应源码逻辑 |
|---|---|---|
iconst_1 | 将常量 1 压入操作数栈 | static int a = 1 |
putstatic #13 | 将栈顶值(1)赋值给静态变量a(#13 是常量池中的a引用) | 完成a=1 |
iconst_2 | 将常量 2 压入操作数栈 | a=2 |
putstatic #13 | 将 2 赋值给a,覆盖原有值 | 完成a=2 |
iconst_3 | 将常量 3 压入操作数栈 | b=3 |
putstatic #19 | 将 3 赋值给静态变量b(#19 是常量池中的b引用) | 完成b=3 |
getstatic #7 | 获取System.out的引用(#47 是常量池中的out引用) | System.out.println(...) |
invokevirtual #6 | 调用makeConcatWithConstants方法 | 拼接a的值 |
invokevirtual #7 | 调用PrintStream.println(String)方法 | 执行打印 |
iconst_4 | 将常量 4 压入操作数栈 | static int b = 4 |
putstatic #19 | 将 4 赋值给b,覆盖之前的 3 | 完成b=4 |
return | 静态构造方法返回 | 结束<clinit>()执行 |
关键发现:
- jclasslib 中
<clinit>()的指令顺序与源码中静态变量、静态代码块的书写顺序完全一致,验证了 “按源文件顺序执行” 的规则; - 即使
b在静态代码块中先赋值(b=3),但后续static int b=4的字节码指令在<clinit>()末尾执行,最终覆盖为 4,与运行结果一致。
3.3 被动引用场景的 jclasslib 验证(以 ConstDemo 为例)
原代码:
packagecom.dwl.ex01_类加载子系统;/** * @Description * @Version 1.0.0 * @Date 2026/2/8 3:39 * @Author Dwl */publicclassPassiveRefDemo3{publicstaticvoidmain(String[] args){// 访问编译期常量(被动引用)System.out.println(ConstDemo.COMPILE_CONST);}}classConstDemo{// 编译期常量staticfinalint COMPILE_CONST =300;static{System.out.println("ConstDemo初始化");}}jclasslib 分析:
- 查看
PassiveRefDemo3.class的main方法字节码:- 核心指令为
sipush 300(直接加载常量 300,而非调用ConstDemo的静态变量) +invokevirtual #15(打印); - 无
getstatic指令访问ConstDemo.COMPILE_CONST,说明编译期常量已直接嵌入到PassiveRefDemo3的常量池中,无需触发ConstDemo的<clinit>()。
- 核心指令为
- 查看
ConstDemo.class的Methods:- 仍能看到
<clinit>()方法(包含静态代码块的打印指令),但运行时未执行,验证 “被动引用不触发初始化” 的规则。
- 仍能看到



三、初始化的触发条件:主动引用
只有当类被主动引用时,才会触发<clinit>()执行。根据《Java 虚拟机规范》,主动引用包括以下场景:
1. 创建类的实例(new关键字)
packagecom.dwl.ex01_类加载子系统;/** * @Description * @Version 1.0.0 * @Date 2026/2/8 3:48 * @Author Dwl */publicclassActiveRefDemo1{static{System.out.println("ActiveRefDemo1初始化");}publicstaticvoidmain(String[] args){// 触发初始化(主动引用)newActiveRefDemo1();}}

jclasslib 补充:main方法中new ActiveRefDemo1()的字节码包含new #7(创建实例) + invokespecial #9(调用<init>()),执行new指令时 JVM 会先触发<clinit>()。
2. 访问类的静态变量或静态方法
packagecom.dwl.ex01_类加载子系统;/** * @Description * @Version 1.0.0 * @Date 2026/2/8 3:54 * @Author Dwl */publicclassActiveRefDemo2{// 静态变量publicstaticint STATIC_FIELD =100;static{System.out.println("ActiveRefDemo2初始化");}publicstaticvoidstaticMethod(){/* ... */}publicstaticvoidmain(String[] args){// 访问静态变量(主动引用)System.out.println(STATIC_FIELD);// 调用静态方法(主动引用)staticMethod();}}
jclasslib 补充:System.out.println(STATIC_FIELD)对应字节码getstatic #13(获取STATIC_FIELD),触发<clinit>()执行。
3. 调用Class.forName()(默认参数)
packagecom.dwl.ex01_类加载子系统;/** * @Description * @Version 1.0.0 * @Date 2026/2/8 3:57 * @Author Dwl */publicclassActiveRefDemo3{static{System.out.println("ActiveRefDemo3初始化");}publicstaticvoidmain(String[] args)throwsClassNotFoundException{// 调用 Class.forName ("类全限定名") 时,默认会触发类的初始化,执行<clinit>() 方法,输出相关初始化信息。Class.forName("com.dwl.ex01_类加载子系统.ActiveRefDemo3");// 主动引用}}4. 反射 API 调用(如Constructor.newInstance())
packagecom.dwl.ex01_类加载子系统;importjava.lang.reflect.Constructor;/** * @Description * @Version 1.0.0 * @Date 2026/2/8 4:05 * @Author Dwl */publicclassActiveRefDemo4{static{System.out.println("ActiveRefDemo4初始化");}publicstaticvoidmain(String[] args)throwsException{Constructor<?> constructor =ActiveRefDemo4.class.getConstructor();// 通过反射 API 调用,如 Constructor.newInstance () 来创建实例时,会触发类的初始化,先执行静态代码块打印相关信息。 constructor.newInstance();// 反射创建实例(主动引用)}}5. 初始化子类时,父类未初始化
packagecom.dwl.ex01_类加载子系统;/** * @Description * @Version 1.0.0 * @Date 2026/2/8 4:06 * @Author Dwl */publicclassActiveRefDemo5{publicstaticvoidmain(String[] args){// 初始化子类,先触发父类初始化newChild();}}classParent{static{System.out.println("Parent初始化");}}classChildextendsParent{static{System.out.println("Child初始化");}}


jclasslib 补充:Parent 和 Child 的 <clinit>() 方法,它们都存在,程序运行时会先打印 Parent初始化,再打印 Child初始化,这是 JVM 保证的父类优先初始化规则。
四、不触发初始化的情况:被动引用
以下场景属于被动引用,不会执行<clinit>()(类可能已被加载但未初始化):
1. 子类访问父类的静态变量(仅父类初始化)
packagecom.dwl.ex01_类加载子系统;/** * @Description * @Version 1.0.0 * @Date 2026/2/8 4:29 * @Author Dwl */publicclassPassiveRefDemo1{publicstaticvoidmain(String[] args){// 子类访问父类静态变量(被动引用)System.out.println(Child2.parentField);}}classParent2{staticint parentField =200;static{System.out.println("Parent初始化");}}classChild2extendsParent2{static{System.out.println("Child初始化");}}

jclasslib 补充:Child2.parentField的字节码是getstatic #13(直接引用父类Parent2.parentField),仅触发Parent2的<clinit>(),Child2的<clinit>()未执行。
2. 数组定义引用类(仅数组类初始化)
packagecom.dwl.ex01_类加载子系统;/** * @Description * @Version 1.0.0 * @Date 2026/2/8 4:36 * @Author Dwl */publicclassPassiveRefDemo2{publicstaticvoidmain(String[] args){// 数组定义(被动引用)Parent3[] parents =newParent3[10];// 输出:class [Lcom.dwl.ex01_类加载子系统.Parent3;System.out.println(parents.getClass());}}classParent3{static{System.out.println("Parent初始化");}}

jclasslib 补充:new Parent3[10]的字节码是anewarray #7(创建数组类),数组类是 JVM 动态生成的(类名[Lcom.by_du.two.Parent3;),原类Parent3的<clinit>()未被触发。
3. 访问编译期常量(static final)
packagecom.dwl.ex01_类加载子系统;/** * @Description * @Version 1.0.0 * @Date 2026/2/8 3:39 * @Author Dwl */publicclassPassiveRefDemo3{publicstaticvoidmain(String[] args){// 访问编译期常量(被动引用)System.out.println(ConstDemo.COMPILE_CONST);}}classConstDemo{// 编译期常量staticfinalint COMPILE_CONST =300;static{System.out.println("ConstDemo初始化");}}五、初始化的关键特性
1. 按源文件顺序执行
<clinit>()中的指令严格按代码中语句的出现顺序执行(静态变量赋值 → 静态代码块 → 后续静态变量赋值),此规则已通过 jclasslib 的字节码分析验证。
2. 父类优先初始化
若类有父类,JVM 保证父类<clinit>()先于子类执行(递归向上至Object类)。
3. 多线程同步加锁
JVM 对<clinit>()方法同步加锁(同一时间仅一个线程执行),避免多线程重复初始化导致的竞态条件:
packagecom.dwl.ex01_类加载子系统;/** * @Description * @Version 1.0.0 * @Date 2026/2/8 4:44 * @Author Dwl */publicclassMultiThreadInitDemo{staticclassInitClass{static{System.out.println(Thread.currentThread().getName()+":进入静态代码块");try{// 模拟初始化耗时Thread.sleep(1000);}catch(InterruptedException e){/* ... */}System.out.println(Thread.currentThread().getName()+":静态代码块执行完毕");}}publicstaticvoidmain(String[] args){// 两个线程同时触发InitClass初始化newThread(InitClass::new,"线程1").start();newThread(InitClass::new,"线程2").start();}}
核心原因:JVM 的类初始化线程安全机制
Java 虚拟机(JVM)对类的初始化过程做了严格的线程安全保障:
- 类的初始化(执行静态代码块、初始化静态变量)是唯一且排他的,一个类在整个生命周期中只会被初始化一次。
- 当第一个线程触发类初始化时,JVM 会为该类加一把「初始化锁」,这个线程会执行完整的静态代码块;其他线程尝试触发该类初始化时,会被阻塞,直到初始化完成,且后续线程不会重复执行静态代码块。
验证:线程 2 其实执行了(只是没走静态代码块)
packagecom.dwl.ex01_类加载子系统;/** * @Description * @Version 1.0.0 * @Date 2026/2/8 4:46 * @Author Dwl */publicclassMultiThreadInitDemo2{staticclassInitClass{static{System.out.println(Thread.currentThread().getName()+":进入静态代码块");try{// 模拟初始化耗时Thread.sleep(1000);}catch(InterruptedException e){System.out.println(Thread.currentThread().getName()+":静态代码块异常");}System.out.println(Thread.currentThread().getName()+":静态代码块执行完毕");}// 新增构造方法,打印实例创建publicInitClass(){System.out.println(Thread.currentThread().getName()+":创建InitClass实例");}}publicstaticvoidmain(String[] args){// 两个线程同时触发InitClass初始化newThread(InitClass::new,"线程1").start();newThread(InitClass::new,"线程2").start();}}
六、初始化流程图
是
否
是
否
类加载请求
是否主动引用?
触发初始化
不触发初始化(被动引用)
递归初始化父类(若有)
执行子类
收集静态变量赋值语句
收集静态代码块语句
按源文件顺序合并为指令
执行指令
是否有静态变量/代码块?
执行赋值/逻辑
跳过(空)
完成初始化,类激活
类可被正常使用:创建实例、访问静态成员
类已加载但未初始化,可直接使用(若无需初始化)
七、jclasslib 分析总结
通过 jclasslib 工具可直观验证类初始化的底层逻辑,核心结论:
<clinit>()是编译器自动生成的静态方法,其字节码指令顺序与源码中静态变量、静态代码块的书写顺序完全一致;- 主动引用场景会触发
<clinit>()的字节码执行,被动引用场景仅访问常量池或数组类,不执行目标类的<clinit>(); - 编译期常量(
static final)会直接嵌入到调用类的常量池中,无需触发原类的初始化。
八、整体总结
类初始化是 Java 类加载的 “最后一公里”,通过<clinit>()方法完成静态资源的激活。核心要点:
- 主动引用触发,被动引用不触发:明确
new、静态访问等场景会初始化,数组定义、编译期常量访问不会(可通过 jclasslib 验证字节码)。 - 顺序与依赖:按代码顺序执行,父类优先于子类,多线程同步加锁保证安全(
<clinit>()的字节码指令是执行顺序的直接体现)。 - 与
<init>()区分:<clinit>()管类(静态),<init>()管对象(实例)。 - 工具辅助:jclasslib 可可视化查看
<clinit>()的字节码,帮助理解类初始化的底层实现。
核心记忆口诀
主动引用初始化,被动引用不沾边;
clinit 管静 init 管例,父类优先序不变;
多线程锁保安全,编译常量嵌池间;
口诀释义
| 口诀分句 | 核心知识点解读 |
|---|---|
| 主动引用才初始化,被动引用不沾边 | 只有new、访问静态成员、反射等主动引用场景触发类初始化;数组定义、访问编译期常量等被动引用不触发 |
| clinit 管静 init 管例,父类优先序不变 | <clinit>()负责类的静态资源(变量 + 代码块),<init>()负责对象实例化;初始化严格按源码顺序执行,且父类初始化优先于子类 |
| 多线程锁保安全,编译常量嵌池间 | JVM 为<clinit>()加同步锁,保证多线程下类只初始化一次;static final编译期常量会直接嵌入调用类的常量池,不触发原类初始化 |