`

虚拟机内加载过程之解析和初始化阶段

阅读更多
解析阶段

解析阶段是虚拟机将常量池中的符号引用转换为直接引用的过程(在验证阶段我们知道,符号引用是将对类自身以外的信息进行匹配性验证,说人话就是说比如一个类A,调用了类B的方法,那么在解析阶段需要看下类A的中的符号能否定位到B类的方法).符号引用例如Constant_Class_info、Constant_Fieldref_info、Constant_Methodref_info 等.

符号引用:是以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可. 符号引用的目标不一定要加载到内存中.
直接引用:是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄. 如果有了直接引用,那引用的目标必定存在于内存中.

虚拟机规范中并没有明确规定解析阶段发生的具体时间,只要求了在执行 anewarray、checkcast、getfield、getstatic、instanceof、invokedynamic、invokeinterface、invokespecial、invokestatic、invokevirtual、ldc、ldc_w、multianewarray、new、putfield、putstatic 用于操作符号引用的字节码指令前,先对它们所使用的符号进行解析. 所以虚拟机实现可以根据需要来判断到底是在类被加载时就对常量池中的符号进行解析,还是等到一个符号将要被使用前才解析它.

除 invokedynamic 指令外,虚拟机实现可以对第一次解析的结果进行缓存(在运行时常量池中记录直接引用,并把常量表示为已解析状态)从而避免解析动作重复进行.

无论是否执行了多次解析,虚拟机需要保证在同一个实体中,如果一个符号引用之前已经被成功解析过,那么后续的引用解析请求就应当一致成功,同样的,第一次解析失败,那么后续的解析应当收到相同的错误.

invokedynamic 指令比较特别,它是只有等到执行到这个指令的时候,才会触发解析动作.

解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄、调用点限定符7类符号引用.

类或接口的解析

比如说类D,要把一个符号引用N解析为一个类或接口C的直接引用,需要如下3个步骤:
1.如果C不是数组类型,那么虚拟机会将N传递给D的类加载器去加载类C,在加载过程中,由于元数据验证、字节码验证等操作可能会触发其他加载动作.
2.如果C是一个数组类型,并且数组的元素类型为对象,那么将会按照第一点的规则加载数据元素类型,如果不是对象,那么将会有虚拟机生成一个代表此数组维度和元素的数组对象.

有疑问。。。

3.如果上面的步骤没有出现任何异常,那么C在虚拟机中实际上已经成为一个有效的类或接口了,但在解析完成之前还要进行符号引用验证,看D是否具备对C的访问权限.

字段解析

要解析一个未被解析过的字段符号引用,首先将会对字段表内 class_index 项中索引的 Constant_class_info 符号引用进行解析,也就是字段所属的类或接口的符号引用.如果在解析过程中出现异常,则解析失败,如果解析成功,那么将这个字段所属的类或接口用C表示,虚拟机规范要求按照如下步骤对C进行后续字段的搜索.

1.如果C本身就包含了简单名称和字段描述符都与目标相匹配的字段,则返回这个字段的直接引用,查找结束.
2.否则,如果在C中实现了接口,将会按照继承关系从下往上递归搜索各个接口和它的父接口,如果接口中包含了简单名称和字段描述符都与目标匹配的字段,则返回这个字段的直接引用,查找结束.
3.否则,如果C不是 java.lang.Object 的话,将会按照继承关系从下往上递归搜索其父类,如果在父类中包含了简单名称和字段描述符都与目标相匹配的字段,则返回这个字段的直接引用,查找结束.
4.否则,查找失败,抛出 NoSuchFieldError 异常.

如果查找过程成返回了引用,则会对这个字段进行权限验证,如果不具备访问权限,则抛出异常.

类方法解析
和字段解析的第一个步骤一样.

1.类方法和接口方法符号引用的常量类型定义是分开的,如果在类方法表中发现 class_index 中索引C是一个接口,那就直接抛出异常.
2.如果通过了第一步,在类C中查找是否具有简单名称和描述符都与目标相匹配的方法,如果有则返回这个方法的直接引用,查找结束.
3.否则,在类C的父类中递归查找是否有简单名和描述符都与目标相匹配的方法,如果有则返回直接引用,查找结束.
4.否则,在类C实现的接口列表及他们的父接口中递归查找是否有简单名和描述符都与目标相匹配的方法,如果存在说明类C是一个抽象类,说明类C是一个抽象类,查找结束,抛出异常.
5.否则,宣告方法查询失败,抛出 NoSuchMethodError.

最后,如果查找成功返回了直接引用,将会对这个方法进行权限验证,如果发现不具备对此方法的访问权限,将抛出异常.

接口方法解析

接口方法也需要先解析出接口方法表的 class_index 项中索引的方法所属的类或接口的符号引用,如果解析成功,依然用C表示这个接口.
1.与类方法解析不同,如果在接口方法表中发现 class_index 中的索引C是一个类而不是接口,那句直接抛出异常.
2.否则,在接口C中查找是否有简单名称和描述符都与目标相匹配的方法,如果有则返回这个方法的直接引用,查找结束.
3.否则,在接口C的父接口中递归查找,直到 java.lang.Object 类(包含Object类)为止,看是否有简单名称和描述符都与目标相匹配的方法,如果有则返回这个方法的直接引用,查找结束.
4.否则,宣告查找失败,抛出异常.

由于接口中所有方法默认都是 public 的,所以不存在访问权限的问题,因此接口方法的符号解析应当不会抛出 IllegalAccessError 异常.


初始化

初始化阶段才真正就是执行类中定义的 java 程序代码.

在初始化阶段,会根据程序员通过程序制定的主观计划去初始化类变量和其他资源,或者可以从另外一个角度来表达:初始化阶段是执行类构造器<clinit>() 方法的过程.

<clinit>() 方法是由编译器自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生,编译器收集的顺序是由语句在源程序中出现的顺序所决定的. 静态语句块中只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块可以赋值,但是不能访问.

<cllinit>() 方法和类的构造函数(<init>() 方法) 不同,它不需要显示调用父类构造器,虚拟机会保证在子类的 <clinit>() 方法执行前,父类的 <clinit>() 方法已经执行完毕. 因此在虚拟机中第一个被执行的 <clinit>() 方法肯定是 Object.

由于父类的 <clinit>() 方法先执行,也就意味着父类中定义的静态语句块要优于子类的变量赋值操作.

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

接口中不能使用静态语句块,但任然有变量初始化的赋值操作,因此接口和类一样会生成 <clinit>() 方法,但接口与类不同的是,执行接口的 <clinit>() 方法不需要先执行父类接口的 <clinit>() 方法. 只有当父接口中定义的变量使用时,父接口才会初始化. 另外,接口的实现类在初始化的时候也一样不会执行接口的 <clinit>() 方法.

虚拟机会保证一个类的 <clinit>() 方法在多线程环境中被正确的加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的 <clinit>() 方法,其他线程都需要阻塞等待,直到活动线程执行完 <clinit>() 方法. 如果在一个类的 <clinit>() 方法中有很多耗时很长的操作,就可能造成多个进程阻塞,在实际应用中这种阻塞往往很隐蔽.
0
1
分享到:
评论

相关推荐

    Java虚拟机JVM类加载初始化

    当一个类被加载、连接、初始化后,它的生命周期就开始了,当代表该类的Class对象不再被引用、即已经不可触及的时候,Class对象的生命周期结束。那么该类的方法区内的数据也会被卸载,从而结束该类的生命周期。一个类...

    解析Java虚拟机中类的初始化及加载器的父委托机制共14页

    解析Java虚拟机中类的初始化及加载器的父委托机制共14页.pdf.zip

    深入理解Java虚拟机-虚拟机类加载机制.xmind

    虚拟机把描述类的数据从Class文件中加载到内存,并对数据进行校验、转换解析和初始化,最终形成可被虚拟机直接使用的Java类型,这就是虚拟机加载机制。

    Java虚拟机类加载机制浅谈

     虚拟机将描述类的数据从Class文件加载到内存,并对数据进行校验、准备、解析和初始化,终会形成可以被虚拟机使用的Java类型,这是一个虚拟机的类加载机制。Java中的类是动态加载的,只有在运行期间使用到该类的...

    12.虚拟机的加载机制1

    1. 加载 2. 验证 【连接】 3. 准备 【连接】 4. 解析 【连接】 5. 初始化 6. 使 7. 卸载 1. 遇到new、getstatic、puts

    关于JVM的总结

    类被加载到虚拟机内存开始,到卸载出内存为止,生命周期包含: 加载,验证,准备,解析,初始化,使用,卸载 7个阶段,加载,验证,准备,初始化和卸载这5个顺序是确定的,解析阶段则不一定,他在某些情况下可以在...

    类加载器和双亲委派模型加载类、类的加载优先级的详解.docx

    其中 验证 、 准备 、 解析 统称为 连接,类的加载主要是前五个阶段,每个阶段基本上保持如上顺序 开始(仅仅是开始,实际上执行是交叉混合的),只有 解析 阶段不一定,在 初始化后 也有可能才开始执行...

    Java虚拟机之类加载机制

    后在运行的时候,虚拟机把描述类的信息从class文件加载到内存,然后再进行校验、解析和初始化等过程,后形成可以被java虚拟机“读懂”的java类型。那么从class——&gt;java虚拟机能“读懂”的java类型是本文要讲解的...

    深入理解java类加载机制

    在类加载方面,我们将深入探讨Java程序的类加载原理和流程,包括加载、验证、准备、解析和初始化等五个环节的详细解析,并对其强调点进行详细讲解。我们将详细介绍Java虚拟机中类的生命周期并探讨类加载时的各种问题...

    解析Java虚拟机中类的初始化及加载器的父委托机制

    主要介绍了Java虚拟机中类的初始化及加载器的父委托机制,包括命名空间等深层次的知识点讲解,需要的朋友可以参考下

    java相关的2024面试题集锦

    - 虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制 - 类加载机制采用懒加载的方式 - 遇到new、getstatic、...

    深入理解Java类加载.docx

    虚拟机把描述类的数据从 Class 文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的 Java 类型,这就是虚拟机的类加载机制。 在Java语言里面,类型的加载、连接和初始化过程都是在...

    Java虚拟机

    这本书的内容是帮你全面了解java虚拟机,本书第1版两年内印刷近10次,98%以上的评论全部为5星级的好评,是整个Java图书领域公认的经典著作和超级畅销书,繁体版在台湾也十分受欢迎。第2版在第1版的基础上做了很大的...

    类加载器深入解析与阶段分解

    在java代码中,类型的加载、连接与初始化过程都是在程序运行期间完成的。 提供了更大的灵活性,增加了更多的可能性。 2.Java虚拟机与程序的生命周期 如下几种情况,虚拟机将结束生命周期: 执行了System.exist() ...

    详解JAVA类加载机制(推荐)

    过程共有七个阶段,其中到初始化之前的都是属于类加载的部分 加载—-验证—-准备—-解析—–初始化—-使用—–卸载 系统可能在第一次使用某个类时加载该类,也可能采用预加载机制来加载某个类,当运行某个java程序时...

    阿里巴巴面试题总结

    答:7个阶段,分别是:加载、验证、准备、解析、初始化、使用和卸载。 2. java有多少个类加载器?分别的作用是什么?一个Class文件是怎么被加载 到JVM里的,描述一下加载流程。 答:java有三个类加载器,分别为:...

    JVM—类加载过程学习

    其实,整个生命周期是7步,类从被加载到虚拟机内存中开始,到卸载出内存为止,分为:加载-&gt;验证-&gt;准备-&gt;解析-&gt;初始化-&gt;使用-&gt;卸载。 2 加载   加载分为三步: 1)通过全类名获取定义此类的二进制字节流; 2)将...

    JVM解毒——类加载子系统

    带着问题,尤其是面试问题的学习才是最高效的。...Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的加载机制

    dai147444612#JVM#HotSpot虚拟机对象探秘1

    对象的创建加载:先去检测new指令能否再常量池中定位到一个类的符号引用,如果未被加载、解析、初始化过 执行相应的类加载过程分配内存: 为对象分配空间时采用指针碰

Global site tag (gtag.js) - Google Analytics