JVM 总结以及调优实战 -(3)类的加载过程

161 阅读6分钟

主要讲述Java类的加载过程。

其实类的加载过程应该放到JVM的第⼀个系列,因为我接触JVM是从JVM内存结构开始,所以就先通过2篇⽂章完成 这块内容的讲解。对于Java类的加载过程,我感觉⾥⾯的内容其实不多,也不那么重要,就把⽹上的资料简单整理 了⼀下,作为了解即可。

一:简介

如果 JVM 想要执⾏这个 .class ⽂件,我们需要将其装进⼀个类加载器 中,它就像⼀个搬运⼯⼀样,会把所有的 .class ⽂件全部搬进JVM⾥⾯来。

image.png

重点知识:

  • Java⽂件经过编译后变成 .class 字节码⽂件
  • 字节码⽂件通过类加载器被搬运到 JVM 虚拟机中
  • 虚拟机主要的5⼤块:⽅法区,堆都为线程共享区域,有线程安全问题,栈和本地⽅法栈和计数器都是独享区 域,不存在线程安全问题,⽽ JVM 的调优主要就是围绕堆,栈两⼤块进⾏。

image.png

二:类加载流程

类加载的过程包括了加载、验证、准备、解析、初始化五个阶段。在这五个阶段中,加载、验证、准备和初始化这 四个阶段发⽣的顺序是确定的,⽽解析阶段则不⼀定,它在某些情况下可以在初始化阶段之后开始,这是为了⽀持 Java语⾔的运⾏时绑定(也成为动态绑定或晚期绑定)。另外注意这⾥的⼏个阶段是按顺序开始,⽽不是按顺序进 ⾏或完成,因为这些阶段通常都是互相交叉地混合进⾏的,通常在⼀个阶段执⾏的过程中调⽤或激活另⼀个阶段。

image.png

2.1 加载

查找并加载类的⼆进制数据加载时类加载过程的第⼀个阶段,在加载阶段,虚拟机需要完成以下三件事情:

  • 通过⼀个类的全限定名来获取其定义的⼆进制字节流。
  • 将这个字节流所代表的静态存储结构转化为⽅法区的运⾏时数据结构。
  • 在Java堆中⽣成⼀个代表这个类的 java.lang.Class对象,作为对⽅法区中这些数据的访问⼊⼝。

2.2 验证

确保被加载的类的正确性

验证是连接阶段的第⼀步,这⼀阶段的⽬的是为了确保Class⽂件的字节流中包含的信息符合当前虚拟机的要求, 并且不会危害虚拟机⾃身的安全。验证阶段⼤致会完成4个阶段的检验动作:

  • ⽂件格式验证:验证字节流是否符合Class⽂件格式的规范;例如:是否以 0xCAFEBABE开头、主次版本号是 否在当前虚拟机的处理范围之内、常量池中的常量是否有不被⽀持的类型。
  • 元数据验证:对字节码描述的信息进⾏语义分析(注意:对⽐javac编译阶段的语义分析),以保证其描述的 信息符合Java语⾔规范的要求;例如:这个类是否有⽗类,除了 java.lang.Object之外。
  • 字节码验证:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。
  • 符号引⽤验证:确保解析动作能正确执⾏。

2.3 准备

为类的静态变量分配内存,并将其初始化为默认值

准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在⽅法区中分配。对于该阶段有以下 ⼏点需要注意:

  • 这时候进⾏内存分配的仅包括类变量(static),⽽不包括实例变量,实例变量会在对象实例化时随着对象⼀ 块分配在Java堆中。
  • 这⾥所设置的初始值通常情况下是数据类型默认的零值(如0、0L、null、false等),⽽不是被在Java代码中 被显式地赋予的值。
  • 如果类字段的字段属性表中存在 ConstantValue属性,即同时被final和static修饰,那么在准备阶段变量 value就会被初始化为ConstValue属性所指定的值。

假设⼀个类变量的定义为:publicstaticintvalue=3,那么变量value在准备阶段过后的初始值为0,⽽不是 3,因为这时候尚未开始执⾏任何Java⽅法,value赋值为3的动作将在初始化阶段才会执⾏。

2.4 解析

把类中的符号引⽤转换为直接引⽤

解析阶段是虚拟机将常量池内的符号引⽤替换为直接引⽤的过程,解析动作主要针对类或接⼝、字段、类⽅法、接 ⼝⽅法、⽅法类型、⽅法句柄和调⽤点限定符7类符号引⽤进⾏。符号引⽤就是⼀组符号来描述⽬标,可以是任何 字⾯量。直接引⽤就是直接指向⽬标的指针、相对偏移量或⼀个间接定位到⽬标的句柄。

2.5 初始化

初始化其实就是⼀个赋值的操作,它会执⾏⼀个类构造器的⽅法。由编译器⾃动收集类中所有变量的赋值动作,此 时准备阶段时的那个static int a = 3 的例⼦,在这个时候就正式赋值为3

2.6 卸载

GC将⽆⽤对象从内存中卸载,Java虚拟机将结束⽣命周期:

  • 执⾏了 System.exit()⽅法
  • 程序正常执⾏结束
  • 程序在执⾏过程中遇到了异常或错误⽽异常终⽌
  • 由于操作系统出现错误⽽导致Java虚拟机进程终⽌

三:类加载器的加载顺序

加载⼀个Class类的顺序也是有优先级的,类加载器从最底层开始往上的顺序是这样的:

  • BootStrap ClassLoader:rt.jar
  • Extention ClassLoader: 加载扩展的jar包
  • App ClassLoader:指定的classpath下⾯的jar包
  • Custom ClassLoader:⾃定义的类加载器

image.png

四:双亲委派机制

双亲委派模型的⼯作流程是:如果⼀个类加载器收到了类加载的请求,它⾸先不会⾃⼰去尝试加载这个类,⽽是把 请求委托给⽗加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只 有当⽗加载器在它的搜索范围中没有找到所需的类时,即⽆法完成该加载,⼦加载器才会尝试⾃⼰去加载该类。

双亲委派机制:

  • 当 AppClassLoader加载⼀个class时,它⾸先不会⾃⼰去尝试加载这个类,⽽是把类加载请求委派给⽗类加 载器ExtClassLoader去完成。
  • 当 ExtClassLoader加载⼀个class时,它⾸先也不会⾃⼰去尝试加载这个类,⽽是把类加载请求委派给 BootStrapClassLoader去完成。
  • 如果 BootStrapClassLoader加载失败(例如在 $JAVA_HOME/jre/lib⾥未查找到该class),会使⽤ ExtClassLoader来尝试加载;
  • 若ExtClassLoader也加载失败,则会使⽤ AppClassLoader来加载,如果 AppClassLoader也加载失败,则会 报出异常 ClassNotFoundException。