本文主要关注类加载
过程中的类初始化
阶段,介绍clinit
方法、主动引用
和被动引用
clinit
类(接口)初始化
阶段是执行**clinit
**方法的过程
接口 clinit
- 接口的
clinit
方法主要用于在接口初始化
时,**初始化接口的域
**(默认是public static final
)
编译
后,接口中的域
,有些会在Class文件常量池
中,有些在clinit
方法中进行初始化
代码
1 2 3 4 5 6 7
| public interface InterfaceClinit { class InnerClass { }
InnerClass INNER_CLASS = new InnerClass(); String NAME = "zhongmingmao"; }
|
字节码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public static final me.zhongmingmao.class_initialization.InterfaceClinit$InnerClass INNER_CLASS; descriptor: Lme/zhongmingmao/class_initialization/InterfaceClinit$InnerClass; flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
public static final java.lang.String NAME; descriptor: Ljava/lang/String; flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL ConstantValue: String zhongmingmao
# <clinit>() 方法 static {}; descriptor: ()V flags: ACC_STATIC Code: stack=2, locals=0, args_size=0 0: new #1 // class me/zhongmingmao/class_initialization/InterfaceClinit$InnerClass 3: dup 4: invokespecial #2 // Method me/zhongmingmao/class_initialization/InterfaceClinit$InnerClass."<init>":()V 7: putstatic #3 // Field INNER_CLASS:Lme/zhongmingmao/class_initialization/InterfaceClinit$InnerClass; 10: return
|
分析
NAME
拥有**ConstantValue
属性,存储在Class文件常量池
**中(Constant pool
),不需要在clinit
方法中进行初始化
INNER_CLASS
需要在clinit
方法中进行初始化,对应的字节码是实现了INNER_CLASS = new InnerClass()
,关于那new
、dup
、invokespecial
等指令的具体含义,可参照博文「字节码 - 方法重载 + 方法重写」,这里不再赘述
类 clinit
类clinit
与接口clinit
最大的区别是允许**static{}
**块
类clinit
由类中的所有**static变量的赋值动作
和static{}块中的语句
**合并产生
的
static{}块
只能读取定义在static{}块
之前的static变量
,但能为定义在static{}块
之后的static变量
赋值
代码
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class ClassClinit { static class InnerClass { }
static { j = 2; }
static int i = 1; static int j; static InnerClass INNER_CLASS = new InnerClass(); }
|
字节码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| static int i; descriptor: I flags: ACC_STATIC
static int j; descriptor: I flags: ACC_STATIC
static me.zhongmingmao.class_initialization.ClassClinit$InnerClass INNER_CLASS; descriptor: Lme/zhongmingmao/class_initialization/ClassClinit$InnerClass; flags: ACC_STATIC
# <clinit>() 方法 static {}; descriptor: ()V flags: ACC_STATIC Code: stack=2, locals=0, args_size=0 0: iconst_2 1: putstatic #2 // Field j:I 4: iconst_1 5: putstatic #3 // Field i:I 8: new #4 // class me/zhongmingmao/class_initialization/ClassClinit$InnerClass 11: dup 12: invokespecial #5 // Method me/zhongmingmao/class_initialization/ClassClinit$InnerClass."<init>":()V 15: putstatic #6 // Field INNER_CLASS:Lme/zhongmingmao/class_initialization/ClassClinit$InnerClass; 18: return
|
空 clinit
clinit
方法对于类或接口
来说**并不是必需
**的,如果一个类中没有static{}块
,也没有对static变量的赋值操作
,那么编译器可以不为这个类生成clinit
方法
代码
1 2 3 4 5 6 7
| public class EmptyClassClinit { }
interface EmptyInterfaceClinit { }
|
隐含地并发
JVM
会保证一个clinit
方法在多线程
环境中被**正确地加锁、同步
** ➜ 隐蔽地阻塞
- 其他线程被阻塞,但如果执行clinit方法的线程退出clinit方法后,其他线程唤醒之后**
不会再次进入
**clinit方法
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| public class ClinitConcurrencyTest { static class A { static { System.out.println("Class A Initialization"); BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); try { reader.readLine(); } catch (IOException e) { e.printStackTrace(); } } }
public static void main(String[] args) {
Runnable initClassTask = () -> { System.out.println(String.format("%s start", Thread.currentThread().getName())); new A(); System.out.println(String.format("%s run over", Thread.currentThread().getName())); };
new Thread(initClassTask, "t1").start(); new Thread(initClassTask, "t2").start(); } }
|
运行结果
1 2 3 4 5 6
| t2 start t1 start Class A Initialization xxxxx # 控制台输入随意字符 t2 run over t1 run over
|
类初始化 vs 接口初始化
- 当
类初始化
时,要求其父类
全部都已经类初始化
过了
- 当
接口初始化
时,并不要求其父接口全部都完成初始化,只有在真正使用父接口
的时候(如引用接口中定义的常量
)才会进行父接口初始化
接口的实现类
在进行类初始化
时,也一样不会进行父接口初始化
主动引用 - 触发类初始化
new指令
代码
1 2 3 4 5 6 7 8 9 10 11
| public class NewTest { static class A { static { System.out.println("Class A Initialization"); } }
public static void main(String[] args) { new A(); } }
|
运行结果
字节码
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=1, args_size=1 # new指令会触发类A的初始化 0: new #2 // class me/zhongmingmao/class_initialization/NewTest$A 3: dup 4: invokespecial #3 // Method me/zhongmingmao/class_initialization/NewTest$A."<init>":()V 7: pop 8: return LocalVariableTable: Start Length Slot Name Signature 0 9 0 args [Ljava/lang/String;
|
putstatic/getstatic指令
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public class StaticTest { static class A { static String name;
static { System.out.println("Class A Initialization"); } }
static class B { static String name;
static { System.out.println("Class B Initialization"); } }
public static void main(String[] args) { A.name = "zhongmingmao"; String name = B.name; } }
|
运行结果
1 2
| Class A Initialization Class B Initialization
|
字节码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=1, locals=2, args_size=1 0: ldc #2 // String zhongmingmao # putstatic指令会触发类A的初始化 2: putstatic #3 // Field me/zhongmingmao/class_initialization/StaticTest$A.name:Ljava/lang/String; # getstatic指令会触发类B的初始化 5: getstatic #4 // Field me/zhongmingmao/class_initialization/StaticTest$B.name:Ljava/lang/String; 8: astore_1 9: return LocalVariableTable: Start Length Slot Name Signature 0 10 0 args [Ljava/lang/String; 9 1 1 name Ljava/lang/String;
|
putstatic
/getstatic
指令会触发类的初始化
invokestatic指令
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class InvokestaticTest { static class A { static { System.out.println("Class A Initialization"); }
static void staticMethod() { } }
public static void main(String[] args) { A.staticMethod(); } }
|
运行结果
字节码
1 2 3 4 5 6 7 8 9 10 11
| public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=0, locals=1, args_size=1 # invokestatic指令会触发类A的初始化 0: invokestatic #2 // Method me/zhongmingmao/class_initialization/InvokestaticTest$A.staticMethod:()V 3: return LocalVariableTable: Start Length Slot Name Signature 0 4 0 args [Ljava/lang/String;
|
反射
Class.forName
Class.forName
方法可以通过参数initialize
来确定是否触发类的初始化
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| public class ClassForNameTest { static class A {
static { System.out.println("Class A Initialization"); } }
static class B {
static { System.out.println("Class B Initialization"); } }
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException { ClassLoader classLoader = ClassForNameTest.class.getClassLoader(); Class.forName(A.class.getName());
Class<?> clazz = Class.forName(B.class.getName(), false, classLoader); System.out.println("Load Class B"); clazz.newInstance(); } }
|
运行结果
1 2 3
| Class A Initialization Load Class B # 说明此时类B尚未初始化 Class B Initialization
|
分析
Class.forName
实际调用的是native
方法(对应的CPP代码暂未研究)forName0
,有一个标志位initialize
,是否进行类的初始化
1 2 3
| private static native Class<?> forName0(String name, boolean initialize, ClassLoader loader, Class<?> caller) throws ClassNotFoundException;
|
Class.newInstance
ClassLoader.loadClass
不会触发类的初始化
Class.newInstance
会触发类的初始化
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class LoadClassTest { static class A {
static { System.out.println("Class A Initialization"); } }
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException { ClassLoader classLoader = LoadClassTest.class.getClassLoader(); Class<?> clazz = classLoader.loadClass(A.class.getName()); System.out.println("Load Class A"); clazz.newInstance(); } }
|
运行结果
1 2
| Load Class A Class A Initialization
|
继承
当初始化一个类的时候,如果发现其父类
还没有进行过初始化,则首先触发其父类的初始化
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public class InheritTest { static class Father {
static { System.out.println("Class Father Initialization"); } }
static class Son extends Father {
static { System.out.println("Class Son Initialization"); } }
public static void main(String[] args) { new Son(); } }
|
运行结果
1 2
| Class Father Initialization Class Son Initialization
|
字节码
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=1, args_size=1 # new指令会触发类Son的初始化,由于其直接父类Father尚未初始化,会首先触发直接父类Father的初始化 0: new #2 // class me/zhongmingmao/class_initialization/InheritTest$Son 3: dup 4: invokespecial #3 // Method me/zhongmingmao/class_initialization/InheritTest$Son."<init>":()V 7: pop 8: return LocalVariableTable: Start Length Slot Name Signature 0 9 0 args [Ljava/lang/String;
|
执行主类
当JVM启动时,用户需要指定一个要执行的主类,JVM会先初始化这个主类
代码
1 2 3 4 5 6 7 8
| public class MainClassTest { static { System.out.println("Class MainClassTest Initialization"); }
public static void main(String[] args) { } }
|
运行结果
1
| Class MainClassTest Initialization
|
被动引用 - 不触发类初始化
static 字段
- 对于
static字段
,只有**直接定义这个字段的类才会被初始化
**
- 通过子类来引用父类中定义的static字段,只会触发父类的初始化而不会触发子类的初始化
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public class StaticFieldTest { static class Father { static String FATHER_CLASS_NAME = Father.class.getName();
static { System.out.println("Class Father Initialization"); } }
static class Son extends Father { static String SON_CLASS_NAME = Son.class.getName();
static { System.out.println("Class Son Initialization"); } }
public static void main(String[] args) { String className = Son.FATHER_CLASS_NAME; } }
|
运行结果
1
| Class Father Initialization
|
字节码
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=1, locals=2, args_size=1 # getstatic指令会触发类的初始化,FATHER_CLASS_NAME在类Father中定义,因此只会触发类Father(及其父类)的初始化 0: getstatic #2 // Field me/zhongmingmao/class_initialization/StaticFieldTest$Son.FATHER_CLASS_NAME:Ljava/lang/String; 3: astore_1 4: return LocalVariableTable: Start Length Slot Name Signature 0 5 0 args [Ljava/lang/String; 4 1 1 className Ljava/lang/String; }
|
引用数组
通过数组
定义来引用类,不会触发类的初始化
代码
1 2 3 4 5 6 7 8 9 10 11
| public class ArrayRefTest { static class A { static { System.out.println("Class A Initialization"); } }
public static void main(String[] args) { A[] as = new A[10]; } }
|
字节码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=1, locals=2, args_size=1 0: bipush 10 # anewarray指令并不会触发类A的初始化 2: anewarray #2 // class me/zhongmingmao/class_initialization/ArrayRefTest$A 5: astore_1 6: return LocalVariableTable: Start Length Slot Name Signature 0 7 0 args [Ljava/lang/String; 6 1 1 as [Lme/zhongmingmao/class_initialization/ArrayRefTest$A; }
|
编译时常量
常量
在编译阶段
会存入Class文件常量池
(Constant pool
)中,编译时便会**直接替换
**,本质上并没有直接引用绑定到定义常量的类,不会触发定义常量的类的初始化
代码
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class ConstantValueTest { static class A { static final String NAME = "zhongmingmao";
static { System.out.println("Class A Initialization"); } }
public static void main(String[] args) { String className = A.NAME; } }
|
字节码
ConstantValueTest$A
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| static final java.lang.String NAME; descriptor: Ljava/lang/String; flags: ACC_STATIC, ACC_FINAL ConstantValue: String zhongmingmao # 存储在Class文件常量池,运行时会存放到运行时常量池
# clinit 方法 static {}; descriptor: ()V flags: ACC_STATIC Code: stack=2, locals=0, args_size=0 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #3 // String Class A Initialization 5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return
|
ConstantValueTest
1 2 3 4 5 6 7 8 9 10 11 12 13
| public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=1, locals=2, args_size=1 # ldc指令直接将常量直接压入操作数栈,这在编译时已经可以确定了,因此直接替换,因此在运行时也不会触发类A的初始化 0: ldc #3 // String zhongmingmao 2: astore_1 3: return LocalVariableTable: Start Length Slot Name Signature 0 4 0 args [Ljava/lang/String; 3 1 1 className Ljava/lang/String;
|