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.子类构造函数