类加载机制


类什么时候加载?

当虚拟机启动时,会先加载main方法所在的主类

除此之外,以下几种情况将触发类加载:

  1. 调用类的静态方法
  2. new对象
  3. 使用反射实例化对象

简单来说,就是需要用到类时就加载类

加载一个类时,如果其父类尚未加载,则会先加载父类

类加载过程

类加载主要有五个过程:

加载

加载其实就是将class文件转换为类对象的过程,共分为三个步骤:

  1. 根据类的全限定名加载对应的class文件
  2. 将其转换为方法区的运行时数据结构(JDK1.7之前),JDK1.7之后是放在堆中
  3. 在内存中生成一个java.lang.Class对象

值得一提的两点:

  • class文件的来源并不一定是本地的class文件,只要是符合class文件结构的字节流都可以,比如来源于网络的二进制流,来源于数据库的二进制流,War包,运行时生成等
  • 数组不由类加载器加载,而是JVM直接创建。

验证

验证就是校验加载的类是否符合虚拟机规范,是否有安全问题。

该阶段非常重要,决定了JVM是否能够承受恶意代码的攻击。

如果你足够信任所运行的代码,可以使用-Xverify:none参数关闭,关闭后可以一定程度上加快类加载过程

验证的主要过程分为以下几步:

步骤 验证内容
文件格式验证 验证class文件是否符合规范,例如:
1. 魔数校验:文件开头是否是CAFA BABE
2. 版本校验:当前JVM是否支持该版本的class
3. 常量池中的常量是否有不被支持的常量
4. 等等
元数据验证 class描述的信息进行校验,确保符合虚拟机规范,例如:
1. 是否对final的类进行了继承
2. 是否有父类
3. 抽象方法是否都已实现
4. 等等
字节码验证 验证程序语法语义是否合法,即操作指令是否合规合法
符号引用验证 该动作发生在将符号引用转换为直接引用时,对常量池中各种信息进行校验,如:
1. 符号引用中引用的类是否存在
2. 符号引用中的类、字段、方法是否可以被当前类访问
3. 等等

准备

在准备阶段,JVM将为static修饰的变量分配内存,并初始化0值。

比如有一个静态变量为:

public static int value = 10;

在准备阶段,value的值是0,而不是10

注意:JDK1.7之前,此处分配内存在方法区,1.7之后在堆

解析

解析就是将符号引用替换为直接引用,这里不得不重点提一下二者之间的区别:

符号引用 直接引用
class文件中,用一组符号来描述一个目标。
例如常量池中CONSTANT_Class_info,描述了一个类的全限定名(如java/lang/Object
指向内存某个位置的指针

那么解析到底是怎么一回事呢?

比如现在有两个类,A和B,其中A中引用了B,在A的常量池中有B的符号引用,如下图:

当JVM的类加载器将A和B加载到内存的方法区(JDK1.7)中时,它们俩都各自有一个内存地址

A中B的符号引用替换为B的内存地址,以便将来使用B时,能够正确找到B

初始化

当一个类中有初始化变量需要赋值静态语句块时,JVM会为其自动生成一个<clinit>初始化方法。

初始化就是执行该方法,这是一个线程安全的方法,父类比子类先执行

接口没有静态语句块,但由于会有变量赋值,因此也会生成<clinit>方法

<clinit>方法就是执行赋值操作以及静态语句块

文章作者: 周君
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 周君 !
评论