这几天看java单例模式的时候,对类的加载机制和初始化顺序产生了疑问。于是在OSC发了一个技术贴关于java内部类加载顺序的问题,现在综合大家的回答总结下。
代码
|
|
代码执行分析
先解释下这段代码:这里主要是有一个父类SingletonFather,它有一个静态内部类(里面包含一个静态属性testInstance和一个静态块)、一个带参数的构造方法SingletonFather()、一个静态属性testOutFather()和一个静态块。然后还有一个类Singleton,它继承了SingletonFather,里面包含的东西和SingletonFather是一样的,不过还有一个main方法,main方法里面new了一个Singleton,并调用了它的getInstance()静态方法。
ok,现在我们开始正题-类的加载机制。从java的语言哲学来看,一切都是对象。对jvm来说,一个类也是一个对象,它是特殊的对象,叫类对象。既然是对象,那必然有它的加载过程。而jvm对类的加载包括3个阶段:加载、链接、初始化。简单来说,加载阶段就是把放在硬盘里面的.class文件加载到内存中;链接阶段是链接一些接口、父类、元素类型等;而初始化就是根据执行类里面的一些属性和方法。在这里,对加载和链接不做深入研究。只研究类的初始化顺序。
现在,我们来看下上面代码的执行结果:
我们来对这个输出结果分析下:
当jvm来到程序的入口函数main方法之前,它需要预先加载main方法的所在类,也就是Singleton类,而加载Singleton类,又需要先加载它的父类SingletonFather。所以,jvm最先初始化的类是SingletonFather。初始化的时候,会先按顺序执行它的静态块和静态属性。所以初始化SingletonFather的时候,会先执行静态属性
这个时候输出
然后按顺序执行到下一个语句,也就是static块
这时候输出
在这里我们需要注意的是父类的静态内部类Inner,因为我们并没有调用这个内部类,所以jvm并不会去初始化它。
然后,我们来到子类Singleton,同理,初始化的时候,jvm先去执行它的静态块和静态属性。所以这个时候执行
在这里我们注意,执行这个语句的时候,它会调用子类的构造方法,而这个构造方法会先执行父类的构造方法,在执行下面的语句,所以这里的输出是
然后按顺序执行下面的静态块
这个时候输出的是:
这个时候,在main方法之前,已经对类完全初始化了。于是jvm开始执行main方法。
在main方法的第一句,我们new了一个子类
按照上面对子类构造函数的执行,不难理解这里会输出
然后执行到下一句
在这里,调用了子类的一个静态方法getInstance
这里,它调用了静态内部类的一个方法testInstance。这时候,我们需要注意,jvm这个时候并没有加载这个内部类。所以jvm会先去加载这个子类内部类Inner并初始化它。这个时候,我们已经对jvm初始化类的顺序有一定的了解了。它会按顺序去执行里面的静态块和静态属性
不难理解,这里也会调用父类的构造方法,所以输出是
总结
到这里整个输出的过程我们分析就结束了。下面我们来总结下整个初始化的顺序:
- 先父后子。初始化时,jvm会先去初始化它的父类,再初始化它本身。而且只有在父类完全被初始化后,才会执行子类的初始化。
- 先静态后非静态,最后构造函数。jvm会根据顺序先执行类里面的静态属性和静态块,然后再按顺序执行非静态块和非静态属性,最后才初始化构造函数(其实构造函数这里并没有执行)。而且需要注意的是,静态的只是在加载字节码(.class文件)时执行一次,不管new多少次。而非静态的每new一次就会执行一次。
- 类只有在调用的时候才会被加载,进而初始化。
- 类只有在jvm加载字节码并初始化后才能被构造成实例对象。
写在最后
后来,OSC中有个朋友说jdk厂家不同,加载顺序会有差异,比如IBM的jdk,这里就不探讨了。如果以后遇到了,会同步更新在这里。关于jvm如何加载字节码的,可以看看这篇文章Java杂谈3——类加载机制与初始化顺序.
研究JDK和JVM还有好长的路要走!