JVM类加载机制简单介绍

article/2025/9/22 4:05:32

本文为《深入理解Java虚拟机JVM高级特效与最佳实践(第三版)》一书的摘要总结

类加载时机

Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这个过程被称做虚拟机的类加载机制。

在Java语言中,类型的加载、连接和初始化过程都是在程序运行期间完成的,这种策略为Java应用提供了极高的扩展性和灵活性,Java天生可以动态扩展的语言特性就是依赖运行期间动态加载动态连接这个特点实现的。

类的生命周期:

其中加载、验证、准备、初始化和卸载这5个阶段的顺序是确定的,解析阶段可能在初始化之前也可能在初始化之后进行。

在遇到以下情况,必须对类进行“初始化”(而加载,验证、准备自然需要在此之前进行):

  • 遇到new,getstatic,putstatic或invokestatic这4条字节指令时,如果类型没有进行过初始化,则需要先触发器初始化阶段。能够生成这4条指令的典型Java代码场景有:

    • 使用new关键字实例化对象的时候
    • 读取或者设置一个类型的静态字段
    • 调用一个类型的静态方法的时候
  • 使用java.lang.reflect包的方法对类型进行反射调用的时候

  • 当初始化类的时候,如果发现其父类还没有进行初始化,则需要先触发器父类的初始化

  • 当虚拟机启动的时候,用户需要指定一个要执行的主类(包含main()方法的类),虚拟机会先初始化这个类

  • 当使用JDK7加入的动态语言支持是,如果一个java.lang.invoke.MethodHandle实例最后的解析结果为REF_getStatic,REF_putStatic,REF_invokeStatic,REF_newInvokeSpecial四种类型的方法句柄,并且这个方法句柄对应的类没有进行初始化,则需要先触发器初始化阶段。

  • 当一个接口中定义了JDK8新加入的默认方法是,如果有这个接口的实现类发生了初始化,那该接口要在其之前被初始化

类的加载过程

加载

在加载阶段,虚拟机主要完成三件事情:

  • 通过一个类的全限定名称来获取定义此类的二进制字节流
  • 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构(这个结构完全由虚拟机实现决定,且只是转换,还没有装入方法区)
  • 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

验证

这一阶段是连接的第一阶段,该阶段的目的是确保Class文件的字节流中包含的信息符合《Java虚拟机规范》的全部约束要求,确保这些信息被当做代码运行后不会危害虚拟机自身的安全。

验证阶段大致会完成下面4个阶段:

  • 文件格式验证

是否以魔数0xCAFEBABE开头;主次版本号是否在当前Java虚拟机接受的范围内;常量池的常量中是否有不被支持的常量类型(检查常量的tag标志)等等;这个阶段主要是保证输入的字节流能够被正确的解析并存入方法区之内,格式上符合描述一个Java类型信息的要求。这个阶段的验证基于二进制流,只有通过了这个验证阶段之后,这段字节流才被允许存入Java虚拟机内存的方法区,所以后面的三个验证阶段全部在方法区的存储结构上进行。

  • 元数据验证

对字节码描述的信息进行语义分析,以保证其描述的信息符合《Java虚拟机规范》的要求,主要验证的点有:

  1. 这个类是否父类(除java.lang.Object外,所有的类都应该有父类)
  2. 这个类的父类是否继承了不允许被继承的类(final修饰的类)
  3. 如果这个类不是抽象类,是否实现了父类或者接口之中要求实现的所有方法
  4. 类中的方法、字段是否与父类产生了矛盾(如覆盖了final字段,不符合规则的方法重载等)
  • 字节码验证

这个阶段是整个验证过程中最复杂的一个阶段,主要目的是通过数据流分析控制流分析,确定程序语义是否是合法的、符合逻辑的。就是对类的方法体(Class文件中的Code属性)进行校验分析,保证被校验的类的方法在运行时不会做出危险虚拟机安全的行为,如:

  1. 保证任何时刻操作数栈的数据类型与指令代码序列都能配合工作
  2. 保证任何跳转指令都不会跳转到方法体以为的字节码指令上
  3. 保证方法体中的类型转换总是有效的
  • 符号引用检验

这个阶段的检验发生在虚拟机将符号引用转化为直接引用的时候,这个转化的工作将在连接的第三个阶段————解析阶段 中发生。

本阶段通常需要校验一下内容:

  1. 符号引用中,通过字符串描述的全限定名能否找到对应的类。
  2. 在指定类中是否存在符合方法的字段描述符及简单名称所描述的方法和字段。
  3. 符号引用中的类、字段、方法的可访问性是否可被当前类访问。

符号引用验证的主要目的是为了确保解析行为能够正常执行。

验证阶段对于迅疾的类加载机制来说,是一个非常重要的、但却不是必须要执行的阶段。如果程序运行的全被代码都已经反复使用和验证过,在生产环境中的实施阶段就可以考虑使用-Xverify:none参数来关闭大部分的类验证措施,一缩短虚拟机类加载的时间。

准备

准备阶段是正式为类中定义的 变量 (静态变量,被static修饰的变量)分配内存并设置类变量初始值的阶段。

从概念上讲,这些变量都应该被在方区中进行分配,但必须注意这个方法区本身是一个 逻辑上 的区域,在JDK7及之前,HotSpot使用永久代来实现方法区时,实现是完全符合这种逻辑概念的;在JDK8及以后,类变量则会随着Class对象一起存在Java堆中。

解析

解析阶段是Java虚拟机将 常量池内的 符号引用替换为直接引用的过程。(就是解析符号引用)

  • 符号引用:符号引用以一组 符号 来描述所引用的目标,符号可以是任何形式的 字面量,只要使用时能无歧义的定位到目标即可。
  • 直接引用:直接引用是指可以直接指向目标的 指针相对偏移量 或者是一个能间接定位到目标的 句柄

类或接口解析

在类D中,将一个从未解析过的符号引用N解析为一个类或者接口C的直接引用,虚拟机的整个解析过程为:

  1. 如果C不是一个数组类型,虚拟机将代表N的全限定名称传递给D的类加载器去加载这个类C。

  2. 如果C是一个数组类型,并且数据的元素为对象,那将会按照第一点的规则加载数组 元素类型

  3. 如果上面两步没有问题,那么C在虚拟机中已经成为一个有效的类或者接口了。

字段解析

  1. 先将字段表内class_index项中索引的CONSTANT_Class_info符号引用进行解析,解析完成后把这个字段所属的类或者接口用C表示;
  2. 如果C本身就包含了简单名称和字段描述符都与与目标匹配的字段,则返回这个字段的直接引用,查找结束。
  3. 否则,如果C中实现了接口,将会按照继承关系从下往上地柜搜索各个接口和它的父接口,如果接口中包含了简单名称和字段描述符都与与目标匹配的字段,则返回这个字段的直接直接引用。
  4. 否则,如果C不是java.lang.Object的话,将会按照继承关系从下往上地柜搜索其父类,如果在父类中包含了简单名称和字段描述符都与与目标匹配的字段,则返回这个字段的直接直接引用。
  5. 否则查找失败。

方法解析

  1. 先将方法表内class_index项中索引的CONSTANT_Class_info符号引用进行解析,解析完成后把这个方法所属的类或者接口用C表示;
  2. 在类C中查找是否有简单名称和方法描述符都与与目标匹配的方法,则返回这个方法的直接引用,查找结束。
  3. 否则,在类C的父类中地柜查找是否有简单名称和方法描述符都与与目标匹配的方法,有则返回这个方法的直接引用。
  4. 否则,在类C实现的接口列表及他们的父接口中递归查找是否有简单名称和方法描述符都与与目标匹配的方法,有则返回这个方法的直接引用。
  5. 否则查找失败。

接口方法解析

  1. 先解析出接口方法表中class_index项中索引的CONSTANT_Class_info符号引用进行解析,解析完成后把这个方法所属的类或者接口用C表示;
  2. 如果发现C是一个类,而不是接口,那么直接爆出异常。
  3. 否则,在接口C中查找是否有简单名称和方法描述符都与与目标匹配的方法,有则返回这个方法的直接引用。
  4. 否则在C的父接口中递归查找,知道java.lang.Obect类为止,看是否有简单名称和方法描述符都与与目标匹配的方法,有则返回这个方法的直接引用。
  5. 在上一步中,可能会找到多个简单名称和方法描述符都与与目标匹配的方法,那将会从多个方法中返回其中一个并结束查找。
  6. 否则查找结束。

初始化

类的初始化时类加载的最后一个步骤,直到这一步,虚拟机才开始执行类中编写的Java程序代码,将主导权移交给应用程序。初始化阶段是执行类构造器<init>()方法的过程。<init>()方法并不是程序员在Java代码中直接编写的方法,而是Javac编译器的自动生成物。

  • <init>()方法是有编译器自动收集类中所有 类变量的赋值动作静态语句块 中的语句合并产生的。顺序由源文件中出现的顺序决定。静态语句块知道能访问到定义在它之前的变量,定义在它之后的变量只能赋值,无法访问。

  • <init>()方法与类的构造函数不同,,它不需要显示的调用父类构造器,Java虚拟机会保证在子类的<init>()方法执行前,父类的<init>()方法已经执行完毕。因此在Java虚拟机中,第一个被执行的<init>()方法一定是java.lang.Object类的。

  • <init>()方法对于类或者接口来说不是必须的,当类既没有赋值操作也没有静态语句块时,或接口没有赋值操作时,编译器就不用为该类或接口生成<init>()方法。

  • 接口中可以有变量初始化的操作,所以也会生成<init>()方法,但是接口与类不同的是,执行接口的<init>()方法不会调用父接口的<init>()方法,因为只有当父接口定义的变量被使用时,父接口才会被初始化。此外,接口的实现类在初始化时也不会执行接口的<init>()方法。

  • Java虚拟机必须保证一个类的<init>()方法在多线程环境下被正确的加锁同步。

类加载器

实现通过一个类的全限定名来获取描述该类的二进制字节流这个动作的代码被称为类加载器。

双亲委派模型

在Java虚拟机的角度来看,只存在两种不同的类加载器:

  • 启动类加载器(Bootstrap ClassLoader):这个类加载器是由C++语言实现的,是虚拟机的一部分
  • 其他所有类加载器:这些类加载器有Java语言实现,独立于虚拟机,并且全部继承自抽象类java.lang.ClassLoader

三层类加载器、类加载器双亲委派模型:

  • 启动类加载器:这个类负责将<JAVA_HOME>\lib目录,或者被-Xbootclasspath参数指定的路径中存放的Java虚拟机能够识别的类库加载到虚拟机的内存中。

  • 扩展类加载器:负责加载<JAVA_HOME>\lib\ext目录中,或者被java.ext.dirs系统变量所制定的路径中的类库。

  • 应用累加载器:应用类加载器是ClassLoader类中的getSystemClassLoader()方法的返回值,所以有些场合也被称为“系统类加载器”。它负责加载用户类路径(ClassPath)上的所有类库。如果应用中没有自定义类加载器,一般情况下这个就是程序中默认的类加载器。

上面图中所展示的各种类加载器的层次关系被称为类的“双亲委派模型”,类加载器之间的关系一般不死以继承的关系来实现的,而是以组合的关系来复用父加载器的代码。

双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都会委派给了启动类加载器。只有当父加载器无法完成加载请求是,子加载器才会尝试自己去完成加载。


http://chatgpt.dhexx.cn/article/0Cb9BUeQ.shtml

相关文章

JVM的类加载机制

一、类加载机制 类的加载指的是将类的.class文件中的二进制数据读入到内存中&#xff0c;将其放在运行时数据区的方法区内****&#xff0c;然后在堆区创建一个java.lang.Class对象&#xff0c;用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的Class对象&…

深入JVM类加载机制

从ClassLoad开始说起 ClassLoader顾名思义就是我们所常见的类加载器,其作用就是将编译后的class文件加载内存当中.在应用启动时,JVM通过ClassLoader加载相关的类到JVM当中.在具体了解ClassLoader之前我们先来了解下JVM的类加载机制. 1. 类加载机制 虚拟机将class文件加载到内…

java面试题-JVM类加载机制

类加载的生命周期&#xff1f; 1. 加载阶段&#xff08;Loading&#xff09; 在Java程序中&#xff0c;当需要使用某个类时&#xff0c;JVM会使用类加载器来查找并加载该类文件。类加载器会首先从文件系统或网络中查找相应的 .class 文件&#xff0c;读取类的二进制数据&#x…

JVM面试 类加载机制

JVM的类加载机制 一、JVM的运行机制 JVM 是用于运行Java字节码的虚拟机&#xff0c;包括一套字节码指令集&#xff0c;一组程序寄存器&#xff0c;一个虚拟机栈&#xff0c;一个虚拟机堆&#xff0c;一个方法区和一个垃圾回收器。JVM运行在操作系统之上&#xff0c;不与硬件设…

JVM--详解类加载机制

JVM--详解类加载机制 转载&#xff1a;https://blog.csdn.net/championhengyi/article/details/78680700 Java虚拟机的体系结构 前面我们探讨了Class文件的结构&#xff0c;如果你还没有学习&#xff0c;将不利于这部分知识的吸收与掌握&#xff0c;所以请移步&#xff1a;JV…

JVM类加载机制

文章目录 概述1. 类加载器2.类加载过程3.双亲委派机制总结 概述 Class文件由类装载器装载后&#xff0c;在JVM中将形成一份描述Class结构的元信息对象&#xff0c;通过该元信息对象可以获知Class的结构信息&#xff1a;如构造函数&#xff0c;属性和方法等&#xff0c;Java允许…

JVM:类加载机制

类加载器 什么是类加载器 ​ 类加载器的作用负责从磁盘中或者网络中加载class文件&#xff0c;classloader只负责加载class文件&#xff0c;类加载器通过一个类的全限定名来获取描述此类的二进制字节流。类加载器虽然用于实现加载动作&#xff0c;但它在Java程序中起到的作用…

JVM类的加载机制

1 类的加载机制 类的加载指的是将类的.class文件中的二进制数据读入到内存中&#xff0c;将其放在运行时数据区的方法区内&#xff0c;然后在堆区创建一个java.lang.Class对象&#xff0c;用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的Class对象&#xf…

jvm之java类加载机制和类加载器(ClassLoader)的详解

手把手写代码&#xff1a;三小时急速入门springboot—企业级微博项目实战--->csdn学院 当程序主动使用某个类时&#xff0c;如果该类还未被加载到内存中&#xff0c;则JVM会通过加载、连接、初始化3个步骤来对该类进行初始化。如果没有意外&#xff0c;JVM将会连续完成3个步…

二叉树的遍历

遍历一棵二叉树有很多种方法。假如用D、L、R分别代表二叉树的根结点、左子树、右子树&#xff0c;那么要遍历这棵二叉树&#xff0c;方法就有6种&#xff1a;DLR、DRL、LDR、LRD、RDL、RLD。一般在遍历时遵循先左后右的原则&#xff0c;因此常用的遍历方法有三种&#xff1a;DL…

二叉树的遍历详解

概述 二叉树的遍历是一个很常见的问题。二叉树的遍历方式主要有&#xff1a;先序遍历、中序遍历、后序遍历、层次遍历。先序、中序、后序其实指的是访问父节点的次序。在遍历过程中&#xff0c;若访问顺序是父节点-左孩子节点-右孩子节点&#xff0c;就是先序遍历&#xff0c;…

“二叉树遍历“详解 以及 二叉树的实现

目录 一.二叉树的遍历 1.二叉树的遍历的解释&#xff1a; 2.二叉树的遍历有三种递归结构 (1) 实现先序遍历&#xff1a; (2) 实现中序遍历&#xff1a; (3) 实现后序遍历&#xff1a; (4) 二叉树的层序遍历 层序遍历代码&#xff1a; 二.二叉树的递归实现相关函数讲解…

二叉树遍历详解

二叉树的遍历方式是最基本&#xff0c;也是最重要的一类题目&#xff0c;我们将从「前序」、「中序」、「后序」、「层序」四种遍历方式出发&#xff0c;总结他们的递归和迭代解法。 一、二叉树定义 二叉树&#xff08;Binary tree&#xff09;是树形结构的一个重要类型…

讲透学烂二叉树(三):二叉树的遍历图解算法步骤及JS代码

二叉树的遍历是指不重复地访问二叉树中所有结点&#xff0c;主要指非空二叉树&#xff0c;对于空二叉树则结束返回。 二叉树的遍历分为 深度优先遍历 先序遍历&#xff1a;根节点->左子树->右子树&#xff08;根左右&#xff09;&#xff0c;有的叫&#xff1a;前序遍历…

二叉树的中序遍历算法(Java三种实现方法)

文章目录 题目一、二叉树的节点定义二、三种遍历方法1.递归算法思想 2.迭代算法思想 3.Morris 中序遍历算法思想 总结 题目 给定一个二叉树的根节点 root &#xff0c;返回它的 中序 遍历 一、二叉树的节点定义 public class TreeNode {int val;TreeNode left;TreeNode righ…

二叉树遍历的几种常见方法

二叉树的遍历方法 一.二叉树分类&#xff1a; 完全二叉树满二叉树扩充二叉树平衡二叉树 二.二叉树的四种遍历方式&#xff1a; 前序遍历&#xff08;先根&#xff0c;再左&#xff0c;最后右&#xff09;中序遍历&#xff08;先左&#xff0c;再根&#xff0c;最后右&#…

二叉树的三种遍历方式

目录 1.二叉树的结构&#xff1a; 2.二叉树的前序遍历&#xff1a; 3.二叉树的中序遍历&#xff1a; 4.二叉树的后序遍历&#xff1a; 5.二叉树前、中、后序的代码实现&#xff1a; 前序遍历函数&#xff1a; 中序遍历函数&#xff1a; 后序遍历&#xff1a; 完整代码&am…

图解二叉树的三种遍历

1、二叉树的遍历 前序遍历&#xff1a;根结点 —> 左子树 —> 右子树 中序遍历&#xff1a;左子树—> 根结点 —> 右子树 后序遍历&#xff1a;左子树 —> 右子树 —> 根结点 层次遍历&#xff1a;仅仅需按层次遍历就可以 前序遍历&#xff1a;1 2 4 5 7…

二叉树的遍历【 详细讲解 】

二叉树的遍历 一共有4种遍历 先看图&#xff0c;对于这个图进行4种遍历的讲解 1、 先序遍历 定义&#xff1a;若二叉树为空&#xff0c;则空操作&#xff1b;否则 &#xff08;1&#xff09;访问根节点&#xff08;2&#xff09;先序遍历左子树&#xff08;3&#…

二叉树的创建及遍历方法

目录 1、二叉树的定义及特点 2、满二叉树和完全二叉树的概念 3、二叉树的存储结构 4、创建二叉树 5、树的四种遍历方法 6、结果及其分析 1、二叉树的定义及特点 二叉树是指树中节点的度不大于2的有序树&#xff0c;它是一种最简单且最重要的树。二叉树的递归定义为&#…