0%

深入理解JVM之Java类加载机制

Class文件结构

任何一个Class文件都对应着唯一一个类或者接口的定义信息

  • 魔数:确定这个文件是否是一个能被虚拟机接受的Class文件
  • 版本号:低版本不能执行高版本Class文件,高版本可以执行低版本
  • 常量池:Class文件中的资源仓库
  • 访问标志:用于识别一些类或者接口层次的访问信息
  • 类索引、父类索引、索引集合:确定类的继承关系
  • 字段表:用于描述接口或者类中声明的变量
  • 方法表:用于描述方法上声明的变量
  • 属性表:Class文件、字段表、方法表都可以携带自己的属性表集合,以用于描述某些场景专有的信息

常量池详细介绍

由于常量池中常量的数量是不固定的,所以在常量池的入口需要放置一项u2类型的数据,代表常量池容量计数值(constant_pool_count)。这个容量计数是从1开始的,第0项位置是用来表达不引用任何一个常量池项目的含义。

常量池主要存放两大类常量,字面量(Literal)和符号引用(Symbolic References)。字面量比较接近Java语言层面常量的概念,如文本字符串、声明为final的常量值等。而符号引用则属于编译原理方面的概念,包括了下面三类常量:

  • 类和接口的元限定名(Full Qualified Name)
  • 字段的名称和描述符(Descriptor)
  • 方法名称和描述符

在Class文件中不会保存各个方法、字段的最终内存布局信息,因此这些字段、方法的符号引用不经过运行期转换的话无法得到真正的内存入口地址,也就无法直接被虚拟机使用。当虚拟机运行时,需要从常量池获得对应的符号引用,再在类创建时或运行时解析、翻译到具体的内存地址之中。

类加载过程

类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个证明周期包括:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸载(Unloading)7个阶段。其中验证、准备、解析3个部分统称为连接(Linking)。

加载

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

  • 通过一个类的全限定名来获取定义此类的二进制字节流。
  • 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
  • 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

验证

验证是连接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身安全。

验证阶段大致会完成以下4个阶段的检验动作:

  • 文件格式验证
  • 元数据验证
  • 字节码验证
  • 符号引用验证

准备

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

首先,这时候进行内存分配的仅包括类变量(被static修饰的变量),而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在Java堆中。

其次,这里所说的初始值,通常情况是数据类型的零值。

相对的,有一些特殊情况:如果类字段的字段属性表中存在ConstantValue,那在准备阶段变量就会初始化为ConstantValue属性所指的值。

解析

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

符合引用(Symbolic References)

符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义的定位到目标即可。

直接引用(Direct References)

直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。如果有了直接引用,那引用的目标必定已经在内存中。

初始化

是类加载过程的最后一步。在准备阶段,变量已经赋过一次系统要求的初始值。而在初始化阶段,则根据程序员的代码去初始化类变量和其他资源。

类加载器

虚拟机设计团队把类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到Java虚拟机外部实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块称为“类加载器”。

双亲委派模型

从Java虚拟机的角度来讲,只存在两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),这个类加载器使用C++实现,是虚拟机自身的一部分;另一种就是所有其他类加载器,这些类加载器都是由Java实现的,独立于虚拟机外部,并且全都继承自抽象类java.lang.ClassLoader

从Java开发人员的角度,类加载器可以分为以下三种:

  • 启动类加载器(Bootstrap ClassLoader),这个类加载器负责将JAVA_HOME/lib类库加载到虚拟机内存中。
  • 扩展类加载器:(Extension ClassLoader):它负责加载JAVA_HOME/lib/ext类库加载到虚拟机内存中。
  • 应用程序类加载器(Application ClassLoader):它负责加载用户类路径上所指定的类库。

双亲委派模型工作过程:

如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求派给父类加载器去完成,每一层的加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。

双亲委派模型优点:

Java类随着它的加载器一起具备了一种带有优先级的层次关系。例如java.lang.Object,无论哪一个类加载器要加载这个类,最终都会委派给处于模型最顶端的启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。

iisheng wechat
微信扫码关注 Coder阿胜