JVM学习之类加载机制

来源:互联网 发布:手机淘宝店招图片大全 编辑:程序博客网 时间:2024/05/21 14:58

个人更多更新的博文欢迎访问我的博客 浅唱

关于JVM系列的文章,都是在读了《深入理解java虚拟机》一书之后的读书笔记总结。

在JAVA语言中,类型的加载、连接和初始化过程都是在程序运行期间完成的,JAVA动态拓展的语言特性就是依赖于运行期动态加载和动态连接这个特点实现的。类从被加载到虚拟机内存中开始到卸载出内存为止,整个生命周期如下图所示:

类加载时机

java虚拟机规范严格规定了有且只有下面五种情况必须立即对类进行“初始化”(加载、验证、准备这三个步骤需要在此之前开始):

  • 遇到new、getstatic、putstatic或invokestatic这四条子节码指令的时候,如果类没有进行过初始化,则需要先触发其初始化。最常见的场景是:使用new关键字实例化对象的时候、读取或者设置一个类的静态字段的时候(被final修饰,已在编译期把结果放入常量池的静态字段除外)以及调用一个类的静态方法的时候。

  • 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。

  • 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发父类的初始化。(一个接口在初始化的时候,并不要求其父借口全部都完成了初始化,只有真正使用父接口的时候才会初始化)

  • 当初始化一个类的时候,用户需要指定一个主要执行的主类,虚拟机会先初始化这个主类。

  • 当使用jdk1.7的动态语言支持时,如果一个java.lang.invoke.methodhandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄对应的类没有进行过初始化,则需要先触发其初始化。

类加载过程

加载

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

  • 通过一个类的权限定名来获取定义此类的二进制字节流。

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

  • 在内存中生成一个代表这个类的java.lang.class对象,作为方法区这个类的各种数据的访问入口。

* 与非数组类的加载阶段不同,数组类本身不通过类加载器创建,它是由java虚拟机直接创建的。如果数组的组件类型时引用类型,那就递归的采用以上的加载过程加载这个组件类型吗,数组将在加载该组件类型的类加载器的类名称上被标识。如果数组的组件类型不是引用类型,java虚拟机将会把数组标记为于引导类加载器相关联 *

类加载完成以后,虚拟机外部的二进制字节流就按照虚拟机所需的格式存储在方法区之中。

加载阶段与连接阶段的部分内容时交叉进行的,加载阶段尚未完成,连接阶段就已经开始,但是这两个阶段的开始时间依然保持着固定的先后顺序。

验证

验证阶段是连接阶段的第一步,会完成以下四个阶段的检验动作。

  1. 文件格式验证。如:是否以魔术数0xcafebabe开头,主次版本号是否在当前虚拟机处理范围之内等
  2. 元数据验证。对类的元数据进行语义校验,保证不存在不符合java语言规范的元数据信息
  3. 字节码验证。通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的
  4. 符号引用验证。发生在虚拟机将符号引用转化为直接引用的时候,这个转化动作将在解析阶段中发生。这可以看作是对类自身以外的信息(常量池中的各种符号引用)进行匹配行检验。

准备

准备阶段正式为* 类变量 *分配内存并赋初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。

解析

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

  • 符号引用

    符号引用以一组符号来描述所引用的目标,符号可以好似任何形式的字面量,只要使用时能无歧义地定位到目标即可。符号引用与虚拟机实现的内存布局无关,引用的目标不一定已经加载到内存中。

  • 直接引用

    直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用是和虚拟机实现的内存布局相关的,同一个符号引用在不同的虚拟机实例上翻译出来的直接引用一般不会相同。如果有了直接引用,那引用的目标必定已经在内存中存在。

解析动作主要针对类或接口、字段、类方法、接口方法四类符号引用进行,分别对应于常量池中的CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info、CONSTANT_InterfaceMethodref_info四种常量类型。

  • 字段解析

    对字段进行解析时,会先在本类中查找是否包含有简单名称和字段描述符都与目标相匹配的字段,如果有,则查找结束;如果没有,则会按照继承关系从下往上递归搜索该类所实现的各个接口和它们的父接口,还没有,则按照继承关系从下往上递归搜索其父类,直至查找结束。

  • 类方法解析

    对类方法的解析与对字段解析的搜索步骤差不多,只是多了判断该方法所处的是类还是接口的步骤,而且对类方法的匹配搜索,是先搜索父类,再搜索接口。

  • 接口方法解析

    与类方法解析步骤类似,只是接口不会有父类,因此,只递归向上搜索父接口就行了。

初始化

初始化阶段是执行类构造器< clinit >()方法的过程。

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

< clinit >()方法与类的构造函数不同,它不需要显式地调用父类构造器,虚拟机会保证在子类的< clinit >()方法执行之前,父类的< clinit >()方法方法已经执行完毕。

本文参考了这篇博文
【深入Java虚拟机】之四:类加载机制

0 0
原创粉丝点击