深入理解java虚拟机——类加载机制

时间:2023-01-02 12:13:21

java语言中,一切都可以看成是对象,而每个对象,都是一个类的在运行时表现出种种属性的实例。那么类,如何工作呢?

加载,链接,初始化

一个类,从class文件形式,到可以被使用,共经历加载,链接,初始化这三个过程,其中:

  • 加载: jvm根据一个字符串的名字查找类或接口类型的二进制表示,并由此二进制表示创建类或接口的过程
  • 链接: 为了让类或接口可以被jvm执行,而将类或接口并入jvm运行时状态的过程
  • 初始化: 执行类或接口初始化方法的过程(此方法非java类的构造方法)

Java虚拟机的启动与运行时常量池

在描述类的加载链接初始化之前,首先需要明确两个概念。首先,关于Java类在程序员使用前的整个创建准备过程,是由Java的类加载机制负责完成,类加载机制运作的核心是类加载器(Class Loader),Java虚拟机的启动便是通过引导类加载器创建一个初始类完成的,紧接着Java虚拟机链接这个初始类,调用它的main方法,在main方法中,虚拟机可以执行指令来链接另外的一些类(可能是用户编写的类);其次,每个class文件中都会有一个常量池表(一种数据结构),在Java虚拟机的方法区中,会保存着与之对应的运行时常量池,常量池中的所有引用为构建该类所有组成部分的符号引用。

加载

类的加载是指Java虚拟机通过类加载器(class loader)将class文件中的二进制数据读入到内存中,并将其放入运行时数据区的方法区(运行时常量池)中,同时Java虚拟机会在运行时数据区的堆中创建一个与该类对应的Class对象,用于封装该类在方法区中的数据结构,该Class对象为程序员提供了访问该类各项属性及方法的接口。

Java虚拟机提供两种类型的类加载器:

Java虚拟机自带的类加载器:
1.引导类加载器(由C++语言编写,为Java虚拟机的组成部分,程序员无法访问。启动类加载器对应的加载 路径为jre\lib目录,java标准库(大部分在rt.jar里)位于该路径)
2.扩展类加载器(Java语言编写,URLClassLoader类的实例。扩展类加载器的默认路径为jre\lib\ext目录,使用Java扩展机制的类位于该路径)
3.系统类加载器(Java语言编写,URLClassLoader类的实例。父加载器是扩展类加载器,系统类加载器从classpath路径下加载类库,它是用户自定义类加载器的默认父加载器)

父加载器跟子加载器并不一定是继承关系

用户自定义的类加载器:
Java虚拟机规范要求用户自定义的类加载器必须继承自ClassLoader类,同时,用户可以定制类的加载方式。

关于类加载器加载class文件的方式,有如下几种:

  • 从本地系统加载class文件
  • 从网络下载class文件
  • 从jar,zip等归档文件中加载class文件
  • 将java文件动态编译成class文件
  • 从专有数据库中提取class文件(流)

Java虚拟机规范允许类加载器在预料到某个类将要被使用的时候预先加载它,在加载该类的过程当中,如果遇到class文件缺失或者存在错误的情况,则类加载器应在程序首次主动使用该类的时候报告LinkageError错误,如果程序一直没有主动使用该类,类加载器则不报错。

链接

当类被加载后,便进入链接阶段。链接就是将已读入内存的二进制数据并入Java虚拟机的运行时环境中去,链接共分三个步骤:

验证:

验证的内容包括:

  • 类文件的结构检查:确保类文件遵从Java类文件的格式规范
  • Java语义检查:确保类本身符合Java语言的语法规定,如abstract,final等关键字的特殊性
  • 字节码验证:确保字节码会被虚拟机正确地执行,检查每个操作码及其对应操作数是否合法
  • 二进制兼容性验证:确保相互引用的的类之间协调一致(例如版本一致)

验证过程中,有可能会抛出VerifyError错误。

准备:

准备阶段的任务是创建类或接口的静态字段,并用默认值初始化这些静态字段,这个阶段不会执行任何虚拟机字节码指令。Jav虚拟机规范强制要求,类的准备阶段必须在类的初始化阶段开始之前完成。

解析:

解析阶段是Java虚拟机将运行时常量池中的符号引用转换为直接引用的过程

  • 符号引用为任意字面量,其所引用的目标不一定已经加载到内存中的
  • 直接引用为指向目标的指针、相对偏移量或能间接定位到目标的句柄,直接引用的目标必须是已经加载到内存中的

初始化

初始化阶段,Java虚拟机为类变量赋予初始值(非默认值,此初始值为用户显式赋予的值),有两种对类变量赋值的方式,一、在变量声明处赋值;二、在静态块中赋值

类变量的赋值动作会按照类变量在文件中的由上到下位置顺序进行,例如:

public class Demo{
public static int a = 1;

static{
a = 2;
}

static{
a = 3;
}

public static void main(String[] args){
System.out.pringln("a = " + a); //此处a值为3
}
}

Java虚拟机规范规定,类的加载和链接阶段必须发生在初始化阶段之前,若一个类在初始化的时候还未进行加载和链接,那么将先进行类的加载和链接。若一个类存在父类,那么初始化这个类之前,要保证父类已经被初始化。

当发生以下六种情况的时候,会触发类的初始化动作,此六种情况被称作对类的主动使用,如下:

  • 新建实例对象
  • 访问类变量,或对类变量赋值
  • 访问类方法
  • 调用反射方法建类,如:Class.forName(“com.lyu.ClassDemo”)
  • 初始化一个类的子类
  • Java虚拟机启动时作为启动类的类(带main方法的入口类)

Tips:当调用ClassLoader类的loadClass方法时,并不会导致类的初始化

初始化之后,被初始化类便可以被程序所使用了。

虚拟机的退出

Java虚拟机退出的条件是,某线程调用了Runtime类或System类的exit方法,或Runtime类的halt方法,并且Java安全管理器允许这次exit或halt操作。