java类初始化顺序及<init>()与<clinit>()的区别猜想

1.<init>()与<clinit>()的区别猜想

根据《深入理解java虚拟机》对<clinit>()的定义为:

类加载的初始化阶段是执行类构造器<clinit>()方法的过程。

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

  • 1.<clinit>()方法与类的构造函数(<init>()方法)不同,虚拟机会保证在子类的<clinit>()方法执行之前,父类的<clinit>()方法已经执行完毕,因此在虚拟机中第一个被执行的<clinit>()方法的类肯定是java.lang.Object。
  • 2.<clinit>()方法对于类或接口来说并不是必需的,如果一个类中没有静态语句块,也没有对类变量的赋值操作,那么编译器可以不为这个类生成<clinit>()方法。
  • 3.接口中不能使用静态语句块,但仍然有变量初始化的赋值操作,因此仍会生成<clinit>()方法,但与类不同的是,执行接口的<clinit>()方法不需要先执行父接口的<clinit>()方法。只有当父类接口中定义的变量使用时,父接口才会初始化,另外,接口的实现类在初始化时也一样不会执行接口的<clinit>()方法。
  • 4.虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确地加锁,同步,如果多个线程同时去初始化一个类,那么只有一个线程去执行这个类的<clinit>()方法,其他线程都需要阻塞等待,直到活动线程执行<clinit>()方法完毕,如果在一个类的<clinit>()方法中有耗时很长的操作,就可能造成多个进程阻塞。

我们可以猜想<init>()方法:

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

我们可以通过class字节码来验证猜想:

验证0:什么都没有

public class InitTest {
	public static void main(String[] args) {
		new InitTest();
	}
}

 编译后查看字节码为:

public com.cc.jvm.InitTest();
  descriptor: ()V
  flags: ACC_PUBLIC
  Code:
    stack=1, locals=1, args_size=1
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return
    LineNumberTable:
      line 3: 0

可以看出生成的<init>()方法只调用了父类的构造方法。

验证1:仅构造函数

public class InitTest {
	public InitTest(){
		System.out.println("constructors init");
	}
	public static void main(String[] args) {
		new InitTest();
	}
}

编译后查看字节码为:

public com.cc.jvm.InitTest();
  descriptor: ()V
  flags: ACC_PUBLIC
  Code:
    stack=2, locals=1, args_size=1
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
       7: ldc           #3                  // String constructors init
       9: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      12: return
    LineNumberTable:
      line 5: 0
      line 6: 4
      line 7: 12

可以看出生成的<init>()方法只包含父类和自己的构造方法。

验证2:构造函数+实例变量

public class InitTest {
	private int field_1 = 15;
	public InitTest(){
		System.out.println("constructors init");
	}
	public static void main(String[] args) {
		new InitTest();
	}
}

 编译后查看字节码为:

public com.cc.jvm.InitTest();
  descriptor: ()V
  flags: ACC_PUBLIC
  Code:
    stack=2, locals=1, args_size=1
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: aload_0
       5: bipush        15                  // 将15加载到操作数栈
       7: putfield      #2                  // Field field_1:I 赋值
      10: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
      13: ldc           #4                  // String constructors init
      15: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      18: return
    LineNumberTable:
      line 5: 0
      line 4: 4
      line 6: 10
      line 7: 18

可以发现<init>()方法中多了为实例变量field_1赋值的字节码。

验证3:构造函数+实例变量+语句块

public class InitTest {
	private int field_1 = 15;
	{
		System.out.println("code block");
	}
	public InitTest(){
		System.out.println("constructors init");
	}
	public static void main(String[] args) {
		new InitTest();
	}
}

 编译后查看字节码为:

public com.cc.jvm.InitTest();
  descriptor: ()V
  flags: ACC_PUBLIC
  Code:
    stack=2, locals=1, args_size=1
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: aload_0
       5: bipush        15
       7: putfield      #2                  // Field field_1:I
      10: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream; 语句块start
      13: ldc           #4                  // String code block
      15: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V  语句块end
      18: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
      21: ldc           #6                  // String constructors init
      23: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      26: return
    LineNumberTable:
      line 8: 0
      line 4: 4
      line 6: 10
      line 9: 18
      line 10: 26

可以发现<init>()方法中多了为语句块操作的字节码。

<init>方法是在一个类进行对象实例化时调用的。实例化一个类有四种途径

  • 1 调用new操作符
  • 2 调用Class或java.lang.reflect.Constructor对象的newInstance()方法
  • 3 调用任何现有对象的clone()方法
  • 4 通过java.io.ObjectInputStream类的getObject()方法反序列化

总结:Java编译器会为它的每一个类都至少生成一个实例初始化方法。在Class文件中,被称为<init>(),内容包括java类或父类中定义了构造方法,或其他非static实例变量被赋了初始值(null也算)。

2.java类初始化顺序

由于JVM的类加载顺序可知,<clinit>()方法先于<init>()方法,所以可以得出以下结论:

单纯类的情况下的执行顺序(无继承):

1.静态变量或静态代码块(看源码顺序)

2.实例变量或实例代码块(看源码顺序)

3.构造方法

有父类情况下的执行顺序:

1.父类静态变量或静态代码块(看源码顺序)

2.子类静态变量或静态代码块(看源码顺序)

3.父类实例变量或实例代码块(看源码顺序)

4.父类构造函数

5.子类实例变量或实例代码块(看源码顺序)

6.子类构造函数

参考文献:

https://blog.csdn.net/vikenpeng/article/details/84176276

https://blog.csdn.net/x_iya/article/details/77097118

https://www.cnblogs.com/fly-piglet/p/8766226.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值