1、子类加载前会先加载父类吗
在加载一个类前,虚拟机总是会试图加载其父类,因此父类的< clinit >总是在子类的< clinit >前被调用,也就是说父类的static块优先级高于子类。
2、哪些类不会生成< clinit >方法?
- 类中没有声明任何类变量(静态变量),也没有静态代码块
- 类中声明了类变量,但是没有明确的对类变量赋值以及使用静态代码块来执行初始化操作
- 类中包含静态常量(static final)修饰的基本数据类型字段,这些字段在链接阶段的准备环节进行赋值。
3、static与final搭配所有的静态常量都是在链接阶段的准备环节进行赋值吗?
不是!如下例子
//在初始化阶段赋值
public static int i = 1
//在链接阶段的准备环节赋值
public static final int I_FINAL = 11
//在初始化阶段赋值
public static Integer i2 = Integer.valueOf(111)
//在初始化阶段赋值
public static final Integer I_FINAL2 = Integer.valueOf(1111)
使用static final修饰的静态常量,赋值的是字面量或者常量,不会涉及到方法构造器的调用则是在链接阶段的准备环节进行赋值,除此之外都是在初始化阶段赋值。
4、< clinit >()的调用会死锁吗?
会。虚拟机会保证一个类的< clinit >()方法在多线程环境中被正确地加锁、同步,当多个线程同时去初始化一个类,那么只会有一个线程去执行这个了的< clinit >()方法,其他线程都需要阻塞等待,直到活动线程执行< clinit >()方法完毕。
正是因为函数< clinit >()带锁线程安全的,因此,如果在一个类的< clinit >()方法中有耗时很长的操作,就可能造成都多个线程阻塞,引发死锁。并且这种死锁是很难发现的,因为看起来它们并没有可用的锁信息。
5、什么情况会触发类的加载?
类的初始化情况:主动使用情况和被动使用情况
主动使用:
Class只有在必须要首次使用的时候才会被装载,Java虚拟机不会无条件地装载Class类型。Java虚拟机规定,一个类或接口在初次使用前,必须要进行初始化。这里指的“使用”是指主动使用。
主动使用的情况如下:(即,如果出现如下情况,则会对类进行初始化操作。而初始化操作之前的加载、验证、准备已经完成)
- 当创建一个类的实例时,比如使用new关键字,或者通过反射、克隆、反序列化
- 当调用类的静态方法时,即使用了字节码invokestatic指令
- 当使用类、接口的静态字段时(final修饰特殊考虑)
- 当使用java.lang.reflect包中的反射类的方法时。比如Class.forName("xx.xxx.xxxx")
- 当初始化子类时,如果发现父类还没有进行过初始化,则需要先触发其父类的初始化
- 如果一个接口定义了default方法,那么直接实现或者间接实现该接口的类的初始化,该接口要在其之前被初始化
- 当虚拟机启动时,用户需要指定一个执行的主类(包括main()方法的那个类),虚拟机会先初始化这个主类
被动使用:
被动使用不会引起类的初始化。也就是说,并不是在代码中出现的类,就一定被加载或者初始化。如果不符合主动使用的条件,类就不会初始化。
- 当访问一个静态字段时,只有真正声明这个字段的类才会初始化(通过子类引用父类的静态变量,不会导致子类初始化)
- 通过数组定义类引用,不会触发此类的初始化
- 引用常量不会触发此类或者接口初始化,因为常量在链接阶段的准备环节就已经被显式赋值了
- 调用ClassLoader类的loadClass()方法加载一个类,并不会对类的主动使用,不会导致类的初始化(但已经进行了装载(loading)阶段,没有初始化(Initialization)阶段)
被动使用,意味着不需要执行初始化环境,意味着不会有< clinit >()的调用