java里面static代码块加载时机理解
一、概述
我们在谈到java里面static代码块的执行时机的时候,我们都知道是它在类被加载的时候被执行,这种说法没有毛病,但是有误区,因为一个类的加载过程并不是一步就可以完成的。 如果从大的整体上来说这句话没有毛病,但是从一个类被加载的过程中去细说就有问题了,因为一个类的加载过程分为主要分为五步:1、加载;2、验证;3、准备;4、解析;5、初始化。 我们的误区主要是在于将类加载和加载看做是一个操作,实际上类加载过程包含加载,而且静态代码块的加载时机是在类加载过程中的第五步中完成的,也就是类加载过程中的初始化阶段。
二、实例
下面我们看一段程序代码以及运行结果,思考一下为什么。 1、程序代码:
package staticTest; public class TestStatic { public static void main(String[] args) { // 通过子类访问父类的静态属性num System.out.println(SubClass.num); } } /** * 父类 . * @author 小柱 . * */ class SuperClass { public static int num = 6; static { System.out.println("父类静态代码块执行了。"); } } /** * 子类 . * @author 小柱 . * */ class SubClass extends SuperClass { static { System.out.println("子类静态代码块执行了。"); } }
2、运行结果:
父类静态代码块执行了。 6
三、分析
首先我们都知道了类中的静态代码块的执行时机其实是在类加载过程中的第五个阶段也就是初始化阶段;但是对于初始化阶段,虚拟机在什么时候会进行类加载的初始化操作呢,搞懂这个上面的实例运行结果原因就迎刃而解了,其实虚拟机规范严格规定了有且只有五种情况必须立即对类进行初始化(当然加载,验证,准备自然也在初始化之前开始了,因为这几个阶段是顺序执行的): 1、遇到new,putstatic,getstatic,invokestatic这四条字节码指令时,如果该类没有进行过初始化,则会触发初始化;生成这四条指令的最常见的java代码场景是:使用new关键字实例化对象的时候、读取或者设置一个类的静态字段(不包括静态常量)的时候,以及调用一个静态方法的时候 2、使用java反射操作的时候 3、当初始化一个类的时候,如果其父类没有被初始化,那么需要先触发其父类的初始化 4、虚拟机启动时,用户需要指定一个要执行的主类(包含main方法的那个类),则虚拟机会初始化这个主类(也可以看作是main方法是一个静态方法导致虚拟机执行该方法时会初始化该类,对应第一种情况) 5、当使用jdk1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、RET_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,那么需要触发其初始化 所以对于上面的程序代码,因为我们通过在main()方法中调用了SubClass.num,也就是调用了父类中的静态属性num,于是就触发了虚拟机对SuperClass类的初始化操作
四、注意点
1、什么情况下会开始类加载过程的第一个阶段:加载?java虚拟机规范中并没有进行强制的约束,这点可以交给虚拟机的具体实现来自由把握;当然这个 ‘交给虚拟机自由把握’ 情况是除了上述的五种情况之外的,因为上述五种会触发类加载过程中的初始化阶段,那么自然会首先开始第一阶段的加载 2、上述触发虚拟机对类的初始化的五种触发条件中的第一条中说明的除静态常量除外指的是,如果将上述程序代码的num属性修改为:public static final int num = 6时,那么程序运行结果将会变为:
6
可以看到这样就没有触发虚拟机对SuperClass类的初始化操作了
五、参考书籍
《深入理解java虚拟机》