JAVA虚拟机类加载机制之类加载的过程

加载阶段:在加载阶段虚拟机要完成以下三件事情:

(1)通过一个类的全限定名来获取定义此类的二进制字节流。

(2)将这个字节流所代表的的静态存储结构转化为方法区的运行时数据结构。

(3)在java堆中生成一个代表这个类的java.lang.Class对象,作为方法区这个数据的访问入口。

加载阶段是开发期可控性最强的阶段,可以使用系统提供的额加载器来完成,也可以由用户自定义的类加载器去完成,开发人员可以通过定义自己的类加载器去控制字节流的获取方式。

加载阶段与连接阶段的部分内容(如一部分的文件格式验证)是交叉进行的,加载阶段尚未完成,连接阶段可能已经开始。

验证阶段: 虚拟机规范规定如果验证到输入的字节流不符合Class文件的存储格式,就跑出一个java.lang.VerifyError或其子类异常。一般虚拟机的实现会完成下面四个阶段的检验过程:

(1)文件格式验证:该阶段的主要目的是保证输入的字节流能正确地解析并存储于方法区之内,格式上符合描述一个java类型信息的要求。

(2)元数据验证:主要目的是对元数据信息进行语义校验,保证不存在不大符合Java语言规范的元数据信息。即对元数据信息中的数据类型进行校验

(3)字节码验证:主要工作时进行数据流和控制流的分析,主要是对类的方法体进行校验。任务是保证校验类的方法在运行时不会做出危害虚拟机安全的行为.

(4)符号引用验证:验证解析阶段中发生的将符号引用转化为直接引用动作,可以看做对自身以外(常量池中的各种符号引用)的信息进行匹配性的校验。目的是确保解析动作能正常执行,无法通过则抛出java.lang.IncompatibleClassChangeError异常的子类,如java.lang.IllegalAccessError, java.lang.NoSuchFieldError,java.lang.NoSuchMethodError等。

验证阶段非常重要,但不是必要的,在实施阶段可以考虑使用-Xverify:none参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间。

准备阶段:是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中进行分配。赋制定值是在初始化阶段在类构造器<clinit>()方法中。常量(static final修饰)在准备阶段就会进行赋为指定值。

 

解析阶段:解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。

初始化阶段:初始化阶段是执行类构造器<clinit>()方法的过程。以下是<clinit>()方法执行过程中可能会影响程序运行行为的一些特点和细节:

l  <clinit>()方法是由编译器自动收集类中的所有类变量的复制动作和静态语句块中的语句合并产生的,静态语句块中可以访问定义在它之后的静态变量但不能对其进行赋值。

l  虚拟机保证父类的<clinit>()方法在子类之前执行完毕。所以虚拟机中第一个被执行的<clinit>()方法的类肯定是java.lang.Object。

l  <clinit>()方法对于类或者接口来说不是必须的,如果没有静态语句块也没有静态变量的复制操作,那么编译器可以不为这个类生成<clinit>()方法。

l  接口因为有变量初始化的复制操作,所以一样会生成<clinit>()方法,接口的实现类在初始化时不会执行接口的<clinit>()方法,接口也不需要先执行父类的<clinit>()方法。

l  虚拟机保证一个雷的<clinit>()方法在多线程环境中被正确的枷锁和同步,如果多个线程同时去初始化一个雷,那么只会有一个线程去执行这个类的<clinit>()方法,其他线程都需要阻塞等待,知道活动线程执行<clinit>()方法完毕。

 

参考资料:《深入理解java虚拟机》--周志明

猜你喜欢

转载自www.cnblogs.com/huster-huwentao/p/9340072.html