Java 类加载与初始化流程

问:简单谈谈类加载顺序流程及实例初始化机制?

答:类初始化机制实质是要包含类加载机制的,类加载机制实质主要就是类加载器原理,类加载器原理的核心是双亲委派父优先级加载,只有这样的机制才保证了类被加载的唯一性,只是类加载器机制是宏观的概述,往细了说就涉及类初始化机制。类从被加载到虚拟机内存中开始到卸载出内存为止的整个生命周期包括了加载、验证、准备、解析、初始化、使用和卸载这 7 个阶段,其中验证、准备和解析这三个部分统称为连接。加载、验证、准备、初始化和卸载这五个阶段的顺序是确定的,类的加载过程必须按照这种顺序按部就班的“开始”(仅仅指的是开始,而非执行或者结束,因为这些阶段通常都是互相交叉的混合进行,通常会在一个阶段执行的过程中调用或者激活另一个阶段),而解析阶段则不一定(它在某些情况下可以在初始化阶段之后再开始,这是为了支持 Java 语言的运行时绑定。

对于生命周期中的加载阶段虚拟机规范中没强行约束,这点可以交给虚拟机的的具体实现自由把握,但是对于生命周期中的初始化阶段,虚拟机规范严格规定了如下几种情况(如果类未初始化会对类进行初始化):

  • 创建类的实例;

  • 访问类的静态变量(除常量,被 final 修辞的静态变量),因为常量一种特殊的变量,编译器把他们当作值(value)而不是域(field)来对待,如果代码中用到了常变量编译器并不会生成字节码来从对象中载入域的值,而是直接把这个值插入到字节码中,这是一种很有用的优化,但是如果你需要改变 final 域的值那么每一块用到那个域的代码都需要重新编译;

  • 访问类的静态方法;

  • 反射(例如 Class.forName("com.package.Test"));

  • 当初始化一个类时发现其父类还未初始化则先触发父类的初始化;

  • 虚拟机启动时定义了 main() 方法的那个类先初始化;

以上情况称为称对一个类进行主动引用,除此种情况之外被称为被动引用(譬如子类调用父类的静态变量,子类不会被初始化,只有父类被初始化,对于静态字段只有直接定义这个字段的类才会被初始化;通过数组定义来引用类不会触发类的初始化;访问类的常量不会初始化类),被动引用均不会触发类的初始化,接口的加载过程与类的加载过程稍有不同,接口中不能使用 static{} 块,当一个接口在初始化时,并不要求其父接口全部都完成了初始化,只有真正在使用到父接口时(例如引用接口中定义的常量)才会初始化。

加载阶段是类加载过程的第一个阶段,虚拟机需要完成三件事情,通过一个类的全限定名来获取定义此类的二进制字节流,将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构,在 Java 堆中生成一个代表这个类的 java.lang.Class 对象作为方法区这些数据的访问入口;该阶段使用类加载器完成,加载阶段与连接阶段的部分内容(如一部分字节码文件格式验证动作)是交叉进行的,加载阶段尚未完成连接阶段可能已经开始。

验证是连接阶段的第一步,目的是为了确保 Class 文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全;不同虚拟机对类验证的实现可能有所不同,但大致都会完成下面四个阶段的验证:文件格式验证、元数据验证、字节码验证和符号引用验证,验证阶段对于虚拟机的类加载机制来说不一定是必要的阶段,如果所运行的全部代码确认是安全的则可以关闭大部分的类验证措施以缩短虚拟机类加载时间。

准备阶段是为类的静态变量分配内存并将其初始化为默认值,这些内存都将在方法区中进行分配,准备阶段不分配类中的实例变量的内存,实例变量将会在对象实例化时随着对象一起分配在 Java 堆中,譬如 public static int value=123; 在准备阶段 value 初始值为 0 ,在初始化阶段才会变为 123。

解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程;符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可,符号引用与虚拟机实现的内存布局无关,引用的目标并不一定已经加载到内存中;直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄,直接引用是与虚拟机实现的内存布局相关的,如果有了直接引用则引用的目标必定已经在内存中存在。

初始化是类加载过程的最后一步,前面的类加载过程除了在加载阶段用户应用程序可以通过自定义类加载器参与之外,其余动作完全由虚拟机主导和控制,到了初始化阶段才真正开始执行类中定义的 Java 程序代码,初始化阶段是执行类构造器 <clinit>() 方法的过程,<clinit>() 方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块( static{} 块)中的语句合并产生的。


属性、方法、构造方法和块都是类中的成员,在创建类的对象时各成员的执行顺序如下:

  • 父类静态成员和静态初始化快,按在代码中出现的顺序依次执行;

  • 子类静态成员和静态初始化块,按在代码中出现的顺序依次执行;

  • 父类的实例成员和实例初始化块,按在代码中出现的顺序依次执行;

  • 执行父类的构造方法;

  • 子类实例成员和实例初始化块,按在代码中出现的顺序依次执行;

  • 执行子类的构造方法;

非静态初始化块主要是用于对象的初始化操作,在每次创建对象的时都要调用一次,其执行顺序在构造方法之前;

由于非静态成员不能在静态方法中使用,同样也不能在静态初始化块中,因此静态初始化块主要用于初始化静态变量和静态方法,静态初始化块只在类第一次加载到内存时调用一次,并非一定要创建对象才执行,静态初始化块比非静态初始化块先执行。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容