init 是对象构造器方法,程序执行 new 一个对象调用该对象类的 constructor 方法时才会执行 init 方法,
clinit 是类构造器方法(类的加载过程), jvm 进行类的加载 —–> 验证 —-> 解析 —–> 初始化
,其中,初始化阶段,jvm会调用 clinit 方法。
init 是 instance 实例构造器,对非静态变量解析初始化;
clinit 是 class 类构造器对静态变量,静态代码块进行初始化。
在准备阶段,变量已经赋过一次系统要求的初始值(注意:如果这个类变量是 static final 的,那么在准备阶段就会根据程序员的意愿完成初始化,而非系统要求的初始值),
而在初始化阶段,则根据程序员通过程序制定的主观计划去初始化类变量和其他资源,或者可以从另外一个角度来表达:初始化阶段是执行类构造器 clinit 方法的过程。
编译器自动收集类中的所有 类变量 的赋值动作 和 静态语句块(static{}块)中的语句合并产生的,
编译器收集的顺序是由语句在源文件中出现的顺序所决定的,静态语句块中,只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块 可以赋值,但是不能访问。
虚拟机会保证在子类的 clinit 方法执行之前,父类的 clinit 方法已经执行完毕。
因此在虚拟机中第一个被执行的 clinit 方法的类肯定是java.lang.Object。
由于父类的 clinit方法先执行,也就意味着父类中定义的静态语句块要优先于子类的变量赋值操作。
接口的 clinit :
接口中不能使用 静态语句块 ,但仍然有变量初始化的赋值操作,因此接口与类一样都会生成 clinit 方法。
与类不同的是,接口的 clinit方法不需要先执行父接口的 clinit方法,只有当父接口中定义的变量使用时,父接口才会初始化。
接口的实现类在初始化时也一样不会执行接口的 clinit方法,只有使用了接口的类变量时才会执行接口的 clinit方法。
类的初始化顺序:
总结:包含父子类和接口类
普通类:
静态变量
静态代码块
普通变量
普通代码块
构造函数
继承的子类:
父类静态变量
父类静态代码块
子类静态变量
子类静态代码块
父类普通变量
父类普通代码块
父类构造函数
子类普通变量
子类普通代码块
子类构造函数
抽象的实现子类: 接口 - 抽象类 - 实现类
接口静态变量
抽象类静态变量
抽象类静态代码块
实现类静态变量
实现类静态代码块
抽象类普通变量
抽象类普通代码块
抽象类构造函数
实现类普通变量
实现类普通代码块
实现类构造函数
JVM的类加载机制中,准备阶段和初始化阶段尤为重要,在程序设计中有时候起到至关重要的作用。因此今天来根据一个例子来讲解JVM中
的过程。
public class JavaTest {
public static void main(String[] args) {
f1();
}
static JavaTest javaTest = new JavaTest();
static {
System.out.println("1");
}
{
System.out.println("2");
}
JavaTest() {
System.out.println("3");
System.out.println("a=" + a + ", b=" + b);
}
public static void f1() {
System.out.println("4");
}
int a = 100;
static int b = 200;
}
运行结果是什么?
JavaTest 程序的入口是 public static void main
, 那么在调用这个 main函数之前,需要执行类的加载过程,类加载成功后才会去调用main方法。
那么,
第一步: 在类加载过程的准备阶段,先对b进行系统的赋值,b = 0
。
第二步: 在类加载过程的初始化阶段,执行
方法,那么先执行类变量的初始化,即:static JavaTest javaTest = new JavaTest();
第三步: 在第二步的时候,
正在执行,而且此时进行类 对象的初始化(new JavaTest()
),会去调用
方法,
因此会首先执行非静态代码块:System.out.println("2")
,
然后执行非静态变量的初始化:a = 100
(此时的先后顺序依照代码编写的先后顺序),
然后执行构造函数:System.out.println("3"); System.out.println("a=" + a + ", b=" + b);
, 此时a的值为100,b的值还是0,因为
还只执行到 static JavaTest javaTest = new JavaTest();
第四步:
方法已经执行完了,那么就接下来执行
剩余的部分,
先执行类的静态代码块:System.out.println("2")
,
再执行类的静态变量初始化:static int b = 200
。(此时的先后顺序依照代码编写的先后顺序),
此时,
方法就执行完成了。
第五步:
和
方法 都已经执行完成了,类已经加载完成,此时就是函数的调用了,JavaTest 的函数入口是 main() 方法,因此会调用静态方法f1():System.out.println("4");
到此,整个程序就执行完成了。
2
3
a=100, b=0
1
4
JVM之类加载的过程(类加载子系统)