`

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

阅读更多
解析阶段

解析阶段是虚拟机将常量池中的符号引用转换为直接引用的过程(在验证阶段我们知道,符号引用是将对类自身以外的信息进行匹配性验证,说人话就是说比如一个类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虚拟机(加载,链接,初始化)1

    总结来说,Java虚拟机的加载、链接和初始化过程是Java程序执行的基础。它们确保了类的正确加载,字节码的安全执行,以及类的静态成员和初始化代码的正确处理。理解这些过程对于优化程序性能、排查运行时错误以及深入...

    12.虚拟机的加载机制1

    类加载的生命周期包括加载、验证、准备、解析、初始化、使用和卸载七个阶段,其中加载、验证、准备、初始化和卸载这五个阶段是确定的,类的加载过程必须按照这种顺序进行。 加载阶段是将.class文件加载到内存中,...

    深入java虚拟机加载初始化

    ### 深入Java虚拟机加载初始化 #### Classloader的作用及意义 在深入了解Java虚拟机(JVM)的加载初始化之前,我们先明确一下`Classloader`的角色。简单地说,`Classloader`的主要职责是将编译后的`.class`文件...

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

    这个过程分为三个阶段:加载、链接和初始化。 1. 加载:JVM通过类加载器寻找并读取类的二进制数据。数据来源可以是.class文件、网络、数据库等。每个类都有一个对应的Class对象,表示该类在JVM中的存在。 2. 链接...

    Java虚拟机JVM类加载初始化

    Java虚拟机JVM类加载初始化是Java程序运行过程中的关键环节,它负责将类的字节码文件加载到内存中并进行相应的处理,以便程序能够正确执行。在Java中,类加载器(Classloader)扮演着核心角色。下面将详细讨论类加载...

    Java虚拟机----类的加载过程.docx

    整个加载过程包括加载(Loading)、连接(Linking)和初始化(Initialization)三个步骤。 加载阶段是类加载的起点,分为三个小步骤: 1. 获取类的二进制字节流,这可能来源于.class文件、jar包或其他来源。 2. 将...

    虚拟机内存图以及加载类的执行过程

    类的加载过程主要包括加载、验证、准备、解析和初始化五个阶段。 - **加载**:查找并加载类的二进制数据。 - **验证**:确保输入的字节流能正确地表示一个类信息。 - **准备**:为类的静态变量分配内存并设置默认...

    java程序初始化顺序

    接口的初始化只涉及到类加载和静态初始化。 6. **类初始化与类加载器**: - 类的初始化是由对应的类加载器触发的,当且仅当以下条件之一满足时: - 遇到new关键字实例化对象; - 遇到静态字段(非final)并对其...

    Java类加载连接和初始化原理解析

    Java类加载连接和初始化是Java虚拟机(JVM)中一个非常重要的机制,它们共同完成了类从加载到初始化的整个过程。下面我们将详细介绍Java类加载连接和初始化原理分析。 一、类加载 类加载是Java虚拟机将类从.class...

    android2.3初始化过程

    Android 2.3的启动过程是一个复杂但有序的过程,涉及到日志系统初始化、配置文件解析、设备初始化、属性服务的启动等多个环节。通过对这些过程的深入了解,我们可以更好地理解Android系统是如何从底层硬件到顶层应用...

    03Java虚拟机是如何加载Java类的1

    【Java虚拟机加载Java类的过程】 Java虚拟机(JVM)加载Java类的过程是一个关键的运行时机制,涉及三个主要步骤:加载、链接和初始化。这个过程确保了类的正确构造和执行。以下是对这些步骤的详细解释: 1. **加载...

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

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

    dalvik虚拟机运行过程分析

    在这个方法中,Zygote进程会进行初始化工作,包括加载系统库、设置安全属性等,然后准备孵化新的应用程序进程。 5. **类加载与DexOpt**:在运行Java代码之前,Dalvik虚拟机需要加载和解析类。这涉及到dex文件...

    Java虚拟机JVM类加载学习笔记

    本文主要探讨JVM的类加载机制,包括类加载、连接、初始化等关键过程,以及类的主动使用和被动使用的情况。 首先,我们要理解**类加载**的作用。JVM的类加载器(ClassLoader)负责将编译后的`.class`文件加载到内存...

    java初始化和清理

    Java类的加载过程可以分为三个阶段:加载、连接和初始化。加载阶段将类的字节码加载到内存中,并在JVM方法区创建对应的Class对象。连接阶段进一步分为验证、准备和解析三个步骤。验证是确保加载的类符合JVM规范,...

    第23讲请介绍类加载过程,什么是双亲委派模型1

    整个过程可以分为五个主要步骤:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)和初始化(Initialization)。在Java虚拟机规范中,这些步骤确保了类的安全性和正确性。 1. 加载...

    java代码初始化流程研究

    类的加载过程包括加载、验证、准备、解析和初始化五个阶段。加载阶段,JVM找到.class文件并读入内存;验证阶段确保字节码的安全性;准备阶段为类的静态变量分配内存并设置默认初始值;解析阶段将符号引用转换为直接...

Global site tag (gtag.js) - Google Analytics