JVM类加载机制

JVM类加载笔记之一

Posted by     "Eric" on Tuesday, December 3, 2019

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

一、概述

3JodpV.png

类的整个生命周期包括:加载,验证,准备,解析,初始化,使用,卸载7个阶段,其中验证、准备、解析3个部分统称为链接。

二、类加载的过程

2.1 加载

“加载”是“类加载”过程的一个阶段,在加载阶段,虚拟机需要完成以下三件事情:

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

  2. 将这个字节流所代表的静态存储结构转化为方法区(元空间)的运行时数据结构,内部采用C++的instanceKlass描述Java类,但是InstanceKlass不能被Java对象直接访问,必须先访问镜像对象,以此作为桥梁,再访问instanceKlass

  3. 在堆中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口

    3Jorm4.png

    上述过程可以用上图来表示,在jdk1.8之后,方法区被移至Metaspace。在元空间中,类文件用C++的instanceKlass这种数据结构来描述,其中有一项_java_mirror指向储存在堆中Class对象,堆中的Class对象同样也持有指向instanceKlass的指针。 每个实例对象都有对象头,其中有一部分是类型指针(有的也没有),该指针指向该类的Class对象。

2.2 验证

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

2.3 准备

为static变量分配内存空间,设置默认值

  • 在jdk1.8中,static变量随Class对象存储在堆中(定义在Class对象内)
  • static变量分配空间和赋值是两个步骤,分配空间在准备阶段完成,赋值在之后的初始化阶段完成(之前介绍的cinit方法)
  • 如果static变量是final的基本类型,以及字符串常量,那么编译阶段就能确定,赋值在准备阶段完成
  • 如果static变量是final的引用类型,那么赋值也会在初始化阶段完成

简而言之,这一阶段为static变量以及static final引用类型的对象分配空间赋“零值”,为static final基本类型对象分配空间且赋值

2.4 解析

将常量池内的符号引用替换为直接引用的过程(简单点理解,符号引用是一个符号,直接引用是地址),流程概括起来就是找到符号链接,判断符号链接所代表的的类或者字段所在的类是否已经被加载,如果加载了换成地址引用,否则进行加载。


  • 符号引用:符号引用存在于字节码文件的常量池中,常量池可以理解为Class文件的资源仓库,Class文件中的字段集合和方法表集合都有指向常量池的index。符号引用包括类和接口的全限定名,字段的名称和描述符,方法的名称和描述符。符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。符号引用于虚拟机实现的内存布局无关,引用的目标不一定已经加载到内存中。比如org.simple.People类引用了org.simple.Language类,在编译时People类并不知道Language类的实际内存地址,用一个符号来表示。
  • 直接引用:可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用和虚拟机实现的内存布局相关,同一个符号引用载不同虚拟机实例上翻译出来的直接引用一般不会相同。如果有了直接引用,那引用的目标必定已经在内存中存在。

2.5 初始化

初始化阶段即调用<clinit><clinit>是由编译器自动收集类中所有类变量的赋值动作和静态语句快中的语句合并产生的,收集顺序按照在源文件中出现的顺序决定。对于定义在静态语句块之后的变量,静态语句块可以赋值,但是不能访问。虚拟机会保证一个类的类构造器<clinit>在多线程环境下能够被正确的加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个类去执行这个类的类构造器,其他多线程都需要阻塞等待。并且当执行<clinit>的线程退出后,其他线程被唤醒后不会再次执行<clinit>.

Java虚拟机规范中没有强制约束什么时候开始加载,但是规定了必须立即对类进行“初始化”的场景:

  1. main方法所在的类,会被首先初始化
  2. 首次访问这个类的静态变量或静态方法时
  3. class.forName
  4. 子类初始化,如果父类还没初始化,会引发
  5. 首次new一个类对象的时候。

以上五种情况被称为类的主动引用。除此之外,所有引用类的方式都不会出发初始化,成为被动引用,有以下几种情况:

  1. 类对象.class不会触发初始化(在加载阶段就已经生成了_java_mirror对象,不需要初始化)
  2. 创建该类的数组不会触发初始化
  3. 类加载器的loadClass方法
  4. Class.forName的参数2位false时
  5. 访问类的static final静态常量(基本类型和字符串)不会初始化