类加载过程之初始化(十)

初始化

类的初始化是类加载的最后一个步骤,在准备阶段时类中的变量都为系统规定的初始值,在初始化阶段会根据程序来进行。
更直接的表达:初始化阶段就是执行类构造器< clinit >()方法过程

< clinit >()方法的产生:
是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{}块)中的语句合并产生的。编译器收集顺序是由语句在源文件中出现的顺序决定的,静态语句块中只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块可以赋值,但是不能访问。

public class Test{
    
    
	static{
    
    
		i = 0; //变量赋值可以正常编译通过
		System.out.print(i);//编译器会提示 “非法向前引用”
	}
	static int i = 1;// 定义在静态语句快之后的变量
}

< clinit >() 方法与类的构造方法(< init >())不同,它不需要显示的调用父类的构造器,Java虚拟机保证在子类的< clinit >() 方法执行前,父类的< clinit >() 已经执行完毕。因此Java虚拟机中第一个执行的< clinit >() 方法的类型肯定是 java.lang.Object。

由于父类的< clinit >() 方法先执行,所以父类中定义的静态语句块要优于子类的变量赋值操作。
例如:

  static class Parent{
    
    
  public static int A = 1;
  static{
    
    
  		A = 2;
	}
}

static class Sub extends Parent{
    
    
	public static int B = A;
}

public static void main(String[] args){
    
    
	System.out.print(Sub.B);
}

输出的为2不为1
  • < clinit >() 方法对于类和接口来说不是必须的,如果一个类没有静态语句块,也没有对变量的赋值操作,那么编译器可以不为这个类生成 < clinit >()方法。
  • 接口中不能使用静态代码块,仍然有变量的赋值操作,接口执行< clinit >()方法不需要先执行父接口的< clinit >()方法,因为只有当父接口定义的变量被使用时才会对父接口初始化,接口的实现类在初始化时也一样不会执行接口的< clinit >()方法。
  • Java虚拟机必须保证一个类< clinit >()方法在多线程环境中被正确地加锁同步,如果多个线程初始化一个类时,会造成其他线程进入阻塞队列,有可能造成进程阻塞。
    注意:其他线程虽然会被阻塞,但如果执行< clinit >()方法的那条线程退出该方法后,其他线程唤醒后也不会在进入该方法了,同一个类加载器下,一个类型只会被初始化一次。

猜你喜欢

转载自blog.csdn.net/weixin_43663421/article/details/109299696