类加载的过程包括:
- 加载,通过一个类的完全限定查找此类的字节码文件,创建一个
Class
对象 - 链接,首先验证字节码的完整性与安全性;然后为静态域分配内存空间,为静态成员赋默认值(
0
,null
,false
);最后如果有必要的话,解析这个类创建的对其他类的所有引用。 - 初始化,若该类有父类,对其进行初始化,执行静态初始化器,初始化静态成员变量。
测试类Test
:
class Test {
final static int b = 7;
static int a = 5;
static {
System.out.println("static block");
}
{
System.out.println("Test Initialize");
}
public Test() {
System.out.println("Test Constructor");
}
}
Test
包括:
- 一个编译期静态常量
final static int b = 7;
- 一个静态成员变量
static int a = 5;
- 静态初始化代码块
static{...}
- 初始化代码块
{...}
- 构造函数
public Test(){...
输出编译期静态常量b
:
public static void main(String[] args) {
System.out.println("Test Load");
System.out.println(Test.b);
}
output:
Test Load
7
从输出结果可以看出,只输出b
,未输出其他初始化代码块内容,所以没有触发类的初始化。
获取Class:
public static void main(String[] args) {
System.out.println("Get Test Class");
Class c = Test.class;
}
output:
Get Test Class
输出结果说明未触发类的初始化,只是触发了类的加载。
输出静态成员变量a
:
public static void main(String[] args) {
System.out.println("Test Initialize");
System.out.println(Test.a);
}
output:
Test Initialize
static block
5
输出结果说明,在调用一个类的静态成员变量时,会触发类的初始化。
连续两次输出a
:
public static void main(String[] args) {
System.out.println("Test Initialize");
System.out.println(Test.a);
System.out.println(Test.a);
}
output:
Test Initialize
static block
5
5
输出结果说明,一个类的静态代码块只在第一次初始化时调用。
创建一个Test
实例:
public static void main(String[] args) {
System.out.println("Create Test Instance");
Test test = new Test();
}
output:
Create Test Instance
static block
Test Initialize
Test Constructor
输出结果说明:
- 创建类的实例会触发类的初始化。
- 静态代码块、初始化代码块、构造函数的触发顺序为:静态代码块->初始化代码块->构造函数。
触发类加载的初始化阶段主要有以下几种:
-
创建类实例、调用类的静态字段(不包括编译期常量)、调用类的静态方法时。
-
使用反射包(java.lang.reflect)的方法对类进行反射调用时,如果类没有初始化,触发类的初始化阶段。
-
当初始化一个类的时候,如果其父类还没进行初始化则需先触发其父类的初始化。
-
当Java虚拟机启动时,用户需要指定一个要执行的主类(包含main方法的类),虚拟机会先初始化这个主类。
-
当使用JDK 1.7 的动态语言支持时,如果一个java.lang.invoke.MethodHandle 实例最后解析结果为REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄对应类没有初始化时,必须触发其初始化。
其他小结:
-
使用
.class
方式获取Class
文件只触发类的加载阶段,而getClass()
和Class.forName()
都会触发类的初始化。 -
静态代码块在类加载的初始化阶段执行,只会在第一次初始化执行。
-
创建类的实例时会执行类的初始化代码块和构造函数,初始化代码块在构造函数之前执行。