《Java虚拟机规范》
关于在什么情况下需要开始类加载过程的的第一个阶段“加载”,《Java虚拟机规范中》并没有进行强制约束,这点可以交给虚拟机的具体实现来自由把握。但是对于初始化阶段,《Java虚拟机规范》则是严格规定了有且只有六种情况必须立即对类进行“初始化”(而加载、验证、准备自然需要在此之前开始)
初始化时机一
遇到new、getstatic、putstatic或invokestatic这四条字节码指令时,如果类型没有进行过初始化,则需要先触发其初始化阶段。能够生成这四条指令的典型Java代码场景如下
场景 | 描述 |
---|---|
一 | 使用new关键字实例化对象的时候 |
二 | 读取或设置一个类型的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外)。可以换一个说法,读取和设置被final 修饰的基本类型和String类型的静态属性除外。 |
三 | 调用一个类型的静态方法的时候 |
1.使用new关键字实例化对象的时候
public class InitClass {
static {
System.out.println("I was initialized~");
}
}
public class Main {
public static void main(String[] args) {
InitClass initClass = new InitClass();
}
}
//控制台输出
I was initialized~
2.读取或设置一个类型的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段)
public class InitClass {
//注意这里没有使用final修饰
public static int a = 666;
static {
System.out.println("I was initialized~");
}
}
public class Main {
public static void main(String[] args) {
System.out.println(InitClass.a); //这里是读取
}
}
//控制台输出
I was initialized~
666
稍安勿躁,我们去常量池一探究竟,不明常量池可以先看这篇文章,作者真的是天秀,https://blog.csdn.net/wangtaomtk/article/details/52267548
javac编译下这个class文件,然后再javap -v 反编译 InitClass这个文件
惊了!666并不在常量池中
那我们再看看下面的方法中的汇编(虚拟机能识别的汇编)代码,666居然在类初始化方法中(静态块),甚至可以看到我们静态块里面执行System.out.println(“I was initialized~”),到底怎么回事?
接下来我将int的数值设置得大一点
public class InitClass {
//注意这里没有使用final修饰
public static int a = 66666666;
static {
System.out.println("I was initialized~");
}
}
public class Main {
public static void main(String[] args) {
System.out.println(InitClass.a);
}
}
//控制台输出
I was initialized~
66666666
但是这次不一样,这次66666666存放的位置是常量池
注意这个ldc,ldc表示将int、float或String常量值从常量池中推送至栈顶,说明即使数据存放在常量池也有可能初始化类。
我查阅了《java虚拟机规范》一书,其中Class文件的属性里有有一个叫ConstantValue属性。
ConstantValue属性作用是通知虚拟机为静态变量赋值。对于非static类型的变量(实例变量)的赋值是在实例构造器方法中进行的。对于类变量有两种方式可以选择(Oracle公司实现javac编译器的选择是如下)
(1)如果同时使用final和staic来修饰一个变量(按照习惯,这里成为常量更贴切)并且这个变量的类型是基本类型或者java.lang.String的话,就会生成ConstantValue属性来进行初始化。
(2)如果这个变量没有被final修饰,或者并非基本类型及字符串,则将会选择在()方法中进行初始化。
原来如此,由于我们没有使用final修饰,则会选择在()方法中进行初始化。自然没有在编译阶段把结果放入常量池,所以会在类初始化阶段给a进行赋值,自然得先初始化
接下来把a这个属性设置为final
public class InitClass {
public static final int a = 666;
static {
System.out.println("I was initialized~");
}
}
public class Main {
public static void main(String[] args) {
System.out.println(InitClass.a);
}
}
//控制台输出
666
3.调用一个类型的静态方法的时候
public class InitClass {
public static void say(){
System.out.println("I'm static method~");
}
static {
System.out.println("I was initialized~");
}
}
public class Main {
public static void main(String[] args) {
InitClass.say();
}
}
//控制台输出
I was initialized~
I'm static method~
初始化时机二
使用java.lang.reflect包的方法对类型进行反射调用的时候
public class InitClass {
static {
System.out.println("I was initialized~");
}
}
public class Main {
public static void main(String[] args) throws ClassNotFoundException {
Class<?> clazz = Class.forName("org.fenixsoft.classloading.InitClass");
Method[] methods = clazz.getMethods();
for(Method method : methods){
System.out.println(method.getName());
}
}
}
//控制台输出
I was initialized~
wait
wait
wait
equals
toString
hashCode
getClass
notify
notifyAll
初始化时机三
当初始化类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
public class SuperClass {
static {
System.out.println("SuperClass init!");
}
}
public class SubClass extends SuperClass{
static {
System.out.println("Subclass init!");
}
}
public class Main {
public static void main(String[] args) {
new SubClass();
}
}
//控制台输出
SuperClass init!
Subclass init!
初始化时机四
当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类
public class Main {
public static void main(String[] args) {
System.out.println("main method");
}
static {
System.out.println("Main init!");
}
}
//控制台输出
Main init!
main method
初始化时机五
当使用JDK7新加入的动态语言支持时,如果一java.lang.invoke.MethodHandle实例最后的解析结果为REF_getStatic、REF_putStatic、REF__invokeStatic、REF__newInvokeSpecial四种类型的方法句柄,并且这个方法句柄对应的类没有进行过初始化,则需要先触发其初始化。
初始化时机六
当一个接口中定义了JDK8新加入的默认方法(被default关键字修饰的默认方法),如果有这个接口的实现类发生了初始化,那该接口要在其之前被初始化。
public class ShowInterfaceInit {
static {
System.out.println("interface init!");
}
}
public interface DefaultInterface {
ShowInterfaceInit init = new ShowInterfaceInit();
default void defaultMethod(){
System.out.println("default Method");
}
}
public class DefaultInterfaceImpl implements DefaultInterface {
static {
System.out.println("DefaultInterfaceImpl");
}
}
public class Main {
public static void main(String[] args) {
new DefaultInterfaceImpl().defaultMethod();
}
}
//控制台输出
interface init!
DefaultInterfaceImpl
default Method
还有更多有趣的东西,后面接着更!