JVM的类加载机制

JVM的类从被加载到虚拟机,到从内存中卸载,可以分为七个步骤,分别是加载, 验证, 准备, 解析, 初始化, 使用以及卸载. 如下图所示, 其中验证、准备和解析又被称统称为是“连接”阶段.

jvm-class-load

加载

虚拟机在加载阶段将类定义加载到内存中,并在方法区中建立相应的运行时数据结构,同时在堆上建立相应的Class对象实例,作为访问方法区运行时数据的入口.

  1. 通过类的全限定名获取类定义的二进制字节流

  2. 根据获取到的二进制字节流在方法区中建立运行时的数据结构

  3. 在堆中建立相应的Class对象实例,作为访问方法区运行时数据结构的入口

这里值得一提的是, 在第1步中”根据类的全限定名获取类定义的二进制字节流”, 并不要求一定要从Class文件中加载,任何可以获取到合法的类定义字节流的途径都可以. 相对于类加载的其它阶段, 类加载的过程(准确地说,是类加载阶段的第1步)是开发者可控性最强的阶段, 开发人员可以根据需要选择从任何需要的地方获取相应的类定义数据.

类加载器

在虚拟中, 类加载的第1步是通过“类加载器”来完成的,类加载器又可以分为不同的层次, 每个类加载器均有父加载器(启动加载器除外), 在加载类对象的时候 ,类加载器采用了的是“双亲委派模型”. (这里). 具体来讲,当类加载器尝试加载某个类之前,它总是先将加载操作“委托”给父加载器执行,只有当父加载器无法加载该类对象时,它才自己尝试加载.

  • 启动加载器(Bootstrap ClassLoader)

  • 扩展加载器(Extend ClassLoader)

  • 应用加载器(Application ClassLoader)

  • 自定义加载器(Custom ClassLoader)

另外,在虚拟机中,任意类的唯一性由类加载器以及这个类本身共同决定. 换句话说,即使是完全相同的一个类,如果加载的时候使用不同的类加载器,那么对于虚拟机而言就是两个不同的类. 在JAVA虚拟机中,类加载器可以细分为四类:

验证

在完成类加载后,紧接着虚拟机要完成的操作就是“验证”, 在这步中虚拟机要完成的对类定义对象的验证,以保证加载的类对象是符合格式规范,且不会做破坏虚拟机的事情. 在验证阶段,虚拟机要完成的动作又可进一步细分为:

  1. 文件格式的验证: 这步验证的主要目的是保证字节流能够在方法区中正确无误地建立运行时数据结构,主要验证的内容包括确认字节流是否符合文件规范的要求,以及文件版本号能否被当前的虚拟机所处理. 在完成这步验证后,类定义的字节流就会在方法区中建立相应的运行时数据结构,后续的验证都会基于方法区中的数据结构来进行.

  2. 元数据的验证: 这步验证主要是对类定义的各种元数据进行确认,保证不会出现不符合JAVA规范的元数据信息

  3. 字节码的验证: 这步验证主要是确保类定义的数据流和控制流没有问题,以确保在类的执行过程中,不会有危及虚拟机安全的动作

  4. 符号引用的验证: 这步验证主要是确保类定义的符号引用都能被正确地解析成直接引用

准备

准备阶段是正式为类变量分配内存并设置初始值的阶段, 这些内存都将在方法区中分配.这句话中,有三个地方值得引起我们的注意:

  1. 为类变量分配内存: 在准备阶段,只会为类变量分配内存,不包括实例变量
  2. 为类变量设置初始值: 这里的“初始值”通常情况下是指数据类型的初始值,而不是开发人员设置的值.
  3. 内存将在方法区中分配: 类变量的内存都在被分配在方法区中,而不是堆上

这里有个例外,就是当类变量被final修饰时,在准备阶段会完成类变量的赋值操作.

1
2
3
4
5
6
7
8
9
1
public static int value = 3;
在完成准备阶段后,value的值会被初始化成0,而不是3(这步是在初始化阶段完成的)
2
public static final int value = 3;
则完成“准备”阶段后,value的值会被初始化成3

解析

类文件的解析过程是将符号引用转化成直接引用的过程.

TODO

初始化

在进入初始化阶段后,虚拟机才真正开始执行程序中定义的代码. 在“准备”阶段,类变量完成了内存分配及初始值的设置,而在这里,类变量将进一步按照程序的设置,完成赋值操作. 或者可以这么说,在初始化阶段,虚拟机调用了类构造器()方法.

这里简单介绍下类构造器()方法的执行规则:

  • ()方法是由编译器收集类中定义的所有类变量的赋值操作和静态语句块合并产生的,收集的顺序由语句在源文件中出现的顺序决定. 其中, 静态语句块中只能访问到定义在静态语句块之前的变量,对于定义在静态语句块之后的变量,在语句块中可以赋值,但不能访问

  • ()方法与实例构造器不同,它不需要显式地调用父类构造器,虚拟机会保证在调用子类()方法之前,已经完成父类()方法的调用.

  • ()方法对于类或接口来讲不是必需的,如果类中没有静态语句块,也没有对类变量进行赋值操作,则可以不用生成()方法

  • 虚拟机会保证类的()方法在多线程环境中被正确地加锁和同步.

参考文献

  1. 类加载机制

  2. JVM类加载机制

示例代码

  1. 类加载的“初始化”1

  2. 类加载的“初始化”2

您的支持将鼓励我继续创作!