一、概述
虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验,转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。
二、Java类加载时机
(1)遇到new、getstatic、putstatic或invokestatic这4条字节码指令时,如果当前类没有进行过初始化,则需要先触发其初始化。生成这4条指令的最常见的Java代码场景是:使用new关键字实例化对象时、读取或设置一个类的静态字段,以及调用一个类的静态方法时。
加载的类信息(包括static常量数据与方法)将进入Java虚拟机的方法区中(左转【JVM】Java内存区域)
(2)使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行初始化则需要先触发其初始化
(3)当初始化一个类时,如果发现其父类还没有进行初始化,则会先触发其父类的初始化。
(4)当虚拟机启动时,用户需要制定一个执行的主类(包含main方法的那个类),虚拟机会最开始初始化那个类
三、类加载的过程
(1)加载-即装载
这个加载过程虚拟机会完成以下3件事情
- 通过一个类的全限定名来获取定义此类的二进制字节流
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
- 在内存中生成一个代表该类的java.lang.class对象(并没有明确规定是在Java堆中,对于HotSpot虚拟机而言,class对象比较特殊,它虽然是对象,但是存放在方法区里面),作为方法区的这个类的各种数据的访问入口
(2)验证
验证时连接阶段的第一步,这一个阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全
- 文件格式验证
- 元数据验证
- 字节码验证
- 符号引用验证
里面的细节太多,如果大家想了解,请参考Java虚拟机规范
(3)准备
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。这个时候进行内存分配的仅仅包括类变量(被static修饰的变量),而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在Java堆中,这个时候分配的数据值是 零值
但是有一种特殊情况,就是被final修饰的变量将会在这一步也初始化实际值,以下那段代码在准备阶段value将被赋值为123
public static final int value = 123;
数据类型 | 零值 |
---|---|
int | 0 |
long | 0L |
short | (short)0 |
char | ‘\u0000’ |
byte | (byte)0 |
boolean | false |
float | 0.0f |
double | 0.0d |
reference | null |
(4)解析
解析阶段是虚拟机将常量池内的符号引用替换成直接引用的过程,static。
- 类或接口的引用解析
- 引用类字段解析
- 引用类方法解析
- 引用接口方法解析
(5)初始化
类初始化阶段是类加载过程的最后一步,前面的类加载过程中,除了在加载阶段用户应用程序可以通过自定义类加载器参与外,其余动作完全是由虚拟机主导口控制,到了初始化阶段,才真正开始执行类中定义的Java程序代码。
这个阶段将为类中的所有类中静态变量的赋值动作static int i = 1;和静态语句块static{}执行实际操作。
public Class Test{
public final static int readyValue = 123;
public static int initValue = 123;
public static int initChangeValue;
static {
//修改initChangeValue
initChangeValue = 1234;
}
}
以上代码的类加载顺序:
(1)加载:类class文件以二进制流的形式加载进JVM中
(2)验证:JVM检验二进制流是否合法安全
(3)准备:final的readyValue将在这个阶段初始化为123,initValue,initChangeValue被初始化为0
(4)初始化:执行对initValue的赋值为123,执行static代码块,将initChangeValue赋值成1234
PS:类加载过程是不对非static字段进行处理的,只是验证阶段验证是否合法。