类加载过程

jvm

Posted by CHuiL on August 10, 2021

一般来说,我们把 Java 的类加载过程分为三个主要步骤:加载、链接、初始化,具体行为在Java 虚拟机规范里有非常详细的定义。

首先是类加载阶段,它是java将字节码数据从不同的数据源读取倒JVM中,并映射为JVM认可的数据结构(Class对象),这里的数据源可能是各种各样的形态,如Jar文件,class文件,甚至ishi网络数据源等;如果输入数据不是ClassFile结构,则会抛出CLassFormatError;

加载阶段是用于参与的阶段,我们可以自定义类加载器。去实现自己的类加载过程。 第二阶段是链接(Linking),这是核心的步骤,简单说是把原始的类定义新限平滑地转化入JVM运行的过程中。这里一共可以分为三步

  • 验证(Verification):这是虚拟机安全的重要保障,JVM需要验证字节信息是否符合Java虚拟机规范,否则就被认为是VerifyError。这样就防止了恶意信息或者不合规的信息危害JVM的运行。验证阶段有可能触发更多class的加载
  • 准备(Praparation):创建类或者接口中的静态变量,并初始化静态变量的初始值。但这里的初始化和下面的显示初始化阶段是有区别的,侧重点在于分配所需要逗得内存空间,不会去执行更进一步的JVM指令。静态原始类型的常量会被直接赋值,而静态变量或者引用类型的常量会在初始化阶段赋值。
  • 解析(Resolution):在这一步会将常量池中的符号引用替换为直接引用。

最后是初始化阶段,这一步真正去执行类初始化的代码逻辑。包括静态变量赋值的动作。以及执行类定义中的静态初始化块内逻辑,编译器在编译阶段就会把这部分逻辑整好,父类型的初始化逻辑优先于当前类型的逻辑。

再来谈谈双亲委派模型,简单说就是当类加载器(Class-Loader)试图加载某个类型的时候,除非父加载器找不到相应类型,否则尽量将这个任务代理给当前加载器的父加载器去做。使用委派模型的目的是避免重复加载 Java 类型。

  • 加载:class字节码文件 -> Class数据结构

image

类加载是将一个.class字节码文件实例化成Class对象并进行相关初始化的操作,jvm会初始化继承树上还没有被初始化过的所有父类,并且会执行这个链路上所有未执行过的静态代码块,静态变量赋值语句等。主要为启动时对类进行Load Link和Init

Load

读取类文件(.class文件)产生二进制流,初步校验cafe babe魔法数,常量池,文件长度,是否有父类等;然后创建对应类的java.lang.Class类

  1. 验证:更详细的验证
  2. 准备阶段:为静态变量分配内存,并设定默认值
  3. 解析:确保类与类之间的相互引用正确,完成内存结构布局
Init

执行类构造器方法,如果赋值运算通过其他类静态方法来完成,会马上解析另外一个类;

类加载器

进行上述操作的,运行时核心基础设施模块;类加载器有权力等级制度,最高层的为Bootstrap,是jvm启动时创建的,通常由于操作系统相关的本地代码实现,是最根基的类加载器,负责最核心的java类加载,如Object,System,String等;
第二层为Platfrom ClassLoader,平台类加载器,用以加载一些扩展系统类,如XML,加密,压缩相关的功能类;
第三层为Application ClassLoader应用类加载器,加载用户定义的CLASSPATH路径下的类;

第二三层的加载类是Java语言实现的;底层的Bootstrap是通过c/c++实现的,他们直接并非继承关系,以组合的方式复用父类加载器功能;

image

从上面的图可以看出类加载器在加载类时,需要先向上层加载器询问其是否已加载,并且他们是否能够加载,均被否定之后才能加载;即优先从父类加载器尝试加载 (双亲委派模式)试想,如果不同类加载器都自己加载需要的某个类型,那么就会出现多次重复加载,完全是种浪费。