探索JVM类加载机制

文章目录

  • 探索JVM类加载机制
    • 1.加载
    • 2.连接
      • 2.1.验证
      • 2.2.准备
      • 2.3.解析
        • 2.3.1.类和接口的解析
        • 2.3.2.字段解析
        • 2.3.3.方法解析
        • 2.3.4.接口方法解析
    • 3.初始化

Why?:

其实,网络上已经有许多有关JVM的干货分享,而且也有许多优质的书籍,例如周志明大佬的《深入理解Java虚拟机》,还有官方发版的《JAVA虚拟机规范》等。但是鄙人每次温故或者解惑的时候,也不会随时随地都有书籍在旁,经常上网查询,而且内容良莠不齐,定位到满足需求文章也浪费时间。所以鄙人就向对技术知识进行具有个人需求特色的归档,同时也温习一下阅读过的优质干货和书籍。

以下内容都是基于《深入理解Java虚拟机》和 Oracle的官方文档JAVA SE 8 虚拟机规范 所总结

探索JVM类加载机制

JVM 将类的字节码数据加载到内存,并对数据进行校验解析和初始化,最终形成可以被运行状态的 JVM 执行的 Class 这一过程称之为类加载机制。注意,这一过程都是在程序运行期间完成的(详见 Loading, Linking, and Initializing)。

在JVM规范中,类加载的过程包含:加载( Loading)- 连接(Linking) - 初始化(Initialization)。而连接又分为验证,准备和解析。

1.加载

Java虚拟机规范中其实该过程为创建与加载,创建主要指类加载器如何委托或定义需加载的类。

加载,查找具有特定名称的类或接口的二进制字节流,并从将该二进制数据加载至内存,并生成一个对应的类或接口。

(1)通过类的全限定名来获取定义该类的二进制字节流(一般指字节码文件)。

(2)将这个字节流锁表示的静态存储结构转化为方法区的运行时数据结构。

(3)在内存中生存一个表示该类的 java.lang.Class 对象,作为方法区这个类的访问入口。

类加载器并不需要等待某个类被首次主动使用时再去加载这个类。JVM虚拟机规范中允许类加载器预料某个类将会被使用就预先加载它(预加载),如果在预先加载的过程中遇到了字节码文件缺失或者存在错误,类加载器必须在程序首次主动使用该类时才报告错误(LinkageError 错误)。如果这个类一直没有被程序主动使用,那么类加载器就不会报错。

在虚拟机启动时会进行预加载,加载JAVA_HOME/lib/下的rt.jar下的字节码,这个jar包里面的内容是java程序运行时必定会用到的,像java.lang.*java.util.*java.io.*等等。

2.连接

连接,将已经加入到内存的类的二进制字节码数据合并到JVM的运行时环境中,并在此期间进行一系列的验证解析。

2.1.验证

字节码文件的详细验证内容可见 Verification of class Files。主要验证内容大致如下:

(1)文件格式验证:魔数、版本号、编码等。

(2)元数据验证:语义校验,不存在与Java语言规范中为定义的元数据信息。

(3)字节码验证:数据流分析和控制流分析,确保代码符合逻辑并不存在不合法的字节码指令。

(4)符号引用验证:将符号引用解析为直接引用时,只会能找到对应的资源如类、方法、字段等。

2.2.准备

为类或接口创建静态字段,并将这些字段初始化为默认值。在此期间不需要执行任何Java虚拟机代码,静态字段的显式初始化是在初始化阶段中类构造器方法<clinit>()中执行,而不是准备阶段。进行内存分配并进行初始化默认值的仅包括静态字段,不包括实例字段。在特殊情况下,也就是如果字段属性为ConstantValue属性,也就是常量字段,就会在准备阶段被初始化为常量值,即ConstantValue所指定的值。

// 静态字段
// 准备阶段初始化为 0
public static int i = 1;// 常量字段
// 准备阶段初始化为 1
public static int i = 1;

2.3.解析

将运行时常量池中的符号引用动态替换为直接引用(具体值)的过程。

在Java 虚拟机规范中,5.4.3. Resolution 指出以下字节码指令 anewarray, checkcast, getfield, getstatic, instanceof, invokedynamic, invokeinterface, invokespecial, invokestatic, invokevirtual, ldc, ldc_w, multianewarray, new, putfield, putstatic 在执行之前必须确保符号引用已解析。

2.3.1.类和接口的解析

如果当前类为D,存在一个还未被解析的符号引用N 指向类或接口C 需要去解析。解析过程如下:

(1)如果当前类C 不是数组类型,那么就会由D的定义类加载器(The defining class loader ),通常意义上代指D的类加载器,根据符号引用N 所表示的全限定名去加载C。在加载过程中,仍会按照类加载机制从头到尾走一遍,所以可能会在验证阶段或者其他阶段出现异常,而导致抛出异常。

(2)如果当前类C 是一个数组类型,并且数组元素类型为引用类型即对象,那么就会按照上一步根据D 的定义类加载器,去加载C中的元素类型,然后再由JVM 去生成一个代表该数组维度和元素的数组对象。

(3)以上步骤执行成功,那么表示C 以及完成了类加载过程,并且C 是有效并可用的。但是还会效验C 的访问权限,如果D 不具备对C 的访问权限,则会 throws an IllegalAccessError

2.3.2.字段解析

如果当前类为D,要解析一个未被解析过的字段引用,首先解析该字段所属的类或接口C 的符号引用(#2.3.1)。因此,任何在类和接口解析过程中失败出现的异常都可能会导致字段解析失败,并抛出相应异常。如果解析成功,就对C 和其父类进行字段搜索:

(1)如果C 中声明了一个名称和描述符与字段引用匹配的字段,则该声明字段为此次字段搜索的结果。

(2)否则,将会根据C 的继承关系从下到上递归从实现的接口列表及其父接口中搜索字段。

(3)否则,将会根据C 的继承关系从下到上递归从父类中搜索字段。

(4)否则,字段搜索失败,throws a NoSuchFieldError

如果字段搜索成功,但是D 不具备该字段的访问权限,则 throws an IllegalAccessError

2.3.3.方法解析

此处与上一小节类似,会先解析方法所属类C 的符号引用,如果解析失败,抛出相应异常。如果类C 解析成功则进行方法搜索:

(1)由于在Class文件格式中类的方法和接口方法的符号引用的常量类型定义是分开的,所以如果类C 是一个接口,则会throws an IncompatibleClassChangeError

(2)在类C 中搜索是否存在简单名称和描述符都与目标匹配的方法,如果有则返回目标方法的直接引用。

(3)在类C 的父类中递归查找,如果有则返回目标方法的直接引用。

(4)在类C 的实现的接口列表及其父类接口中递归查找,如果有则返回目标方法的直接引用。

此处特别说明一下,在《深入理解Java虚拟机》中有关这一步的说明任务类C是一个抽象类,并抛出异常。但是在JavaSE 8的虚拟机规范中5.4.3.3. Method Resolution 有关这一步的说明如下:

Otherwise, method resolution attempts to locate the referenced method in the superinterfaces of the specified class C:

  • If the maximally-specific superinterface methods of C for the name and descriptor specified by the method reference include exactly one method that does not have its ACC_ABSTRACT flag set, then this method is chosen and method lookup succeeds.
  • Otherwise, if any superinterface of C declares a method with the name and descriptor specified by the method reference that has neither its ACC_PRIVATE flag nor its ACC_STATIC flag set, one of these is arbitrarily chosen and method lookup succeeds.
  • Otherwise, method lookup fails.
  • 如果在C中存在最具体的父接口方法与方法引用所指定的名称和描述符都匹配的方法并且没有设置ACC_ABSTRACT标记,那么方法查找成功。
  • 否则,如果C中的任意父接口中声明了一个与方法引用所指定的名称和描述符都匹配的方法,并且既没有设置ACC_PRIVATE标记也没有设置ACC_STATIC标记,那么将会主观选择一个方法,查找成功。
  • 否则,查找失败。

A maximally-specific superinterface method of a class or interface C for a particular method name and descriptor is any method for which all of the following are true:

  • The method is declared in a superinterface (direct or indirect) of C.
  • The method is declared with the specified name and descriptor.
  • The method has neither its ACC_PRIVATE flag nor its ACC_STATIC flag set.
  • Where the method is declared in interface I, there exists no other maximally-specific superinterface method of C with the specified name and descriptor that is declared in a subinterface of I.

在C中最具体的父接口方法,需满足特定的方法名称和描述符,并且满足以下所有条件:

  • 该方法声明在C的父接口(直接或间接)中
  • 该方法能匹配上指定的方法名称和描述符
  • 该方法未设置ACC_PRIVATE或者ACC_STATIC标记
  • 如果该方法被声明在接口 I 中,那么在 I 的子接口中将不会存在任何其他匹配指定方法名称和描述符的在C中最具体父接口方法。

(5)否则,方法解析失败 throws a NoSuchMethodError

如果方法查找成功,但是由于D 不具备该方法的访问权限,则 throws an IllegalAccessError

2.3.4.接口方法解析

先解析方法所属接口C 的符号引用,如果解析失败,抛出相应异常。如果接口C 解析成功则进行方法搜索:

(1)如果C不是接口,则会throws an IncompatibleClassChangeError

(2)在接口C 中搜索是否存在简单名称和描述符都与目标匹配的方法,如果有则返回目标方法的直接引用。

(3)否则,沿着父接口递归查找,直到 java.lang.Object 类(接口查找范围也包括了Object类中的方法,因为本质上接口就是类),并且目标方法设置了ACC_PUBLIC标记,不包含ACC_STATIC标记。

(4)否则,方法解析失败, throws a NoSuchMethodError

3.初始化

类或接口的初始化包括执行其类或接口初始化方法方法。

类或接口最多有一个类或接口初始化方法,并通过调用该方法进行初始化。类或接口的初始化方法具有特殊名称<clinit>(),不接受任何参数,并且返回类型为void。

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

public class Test {static {i = 10;System.out.println(i); // Illegal forward reference}static int i = 0;
}

在Java虚拟机规范中规定,只能有且只有以下六种情况会对类进行初始化:

  • 当遇到 new, getstatic, putstatic, or invokestatic 这四条字节码指令时,如果指令中引用的类型还未初始化,会触发该类型初始化。
    • new:实例化对象
    • getstatic, putstatic:获取或者设置类变量
    • invokestatic:调用静态方法
  • 使用 java.lang.reflect 包中的方法对类型进行反射调用,如果类型还未初始化,会触发其初始化。
  • 如果 java.lang.invoke.MethodHandle 实例的解析结果为 REF_getStatic, REF_putStatic, REF_invokeStatic, REF_newInvokeSpecial这四种类型中的一种,如果类型还未初始化,会触发其初始化。
  • 子类进行初始化,但是其父类还未初始化,会先初始化父类。
  • 如果类型继承了一个声明了non-abstract, non-static 方法的接口,并且调用了该方法(jdk8中接口新增了default方法),会导致此接口初始化。
  • 当虚拟机启动时,指定执行的主类,也就是main()所在的类。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/801946.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【学习】软件验收测试,能否选择第三方检测机构进行测试?

随着信息技术的快速发展,软件已经成为各行各业中不可或缺的一部分。为了保证软件的质量和稳定性,验收测试成为了软件开发过程中至关重要的一环。那么,第三方软件测试机构可以做验收测试吗?我们一起来看下今日的分享。 一、验收测…

MySQL操作DML

目录 1.概述 2.插入 3.更新 4.删除 5.查询 6.小结 1.概述 数据库DML是数据库操作语言(Data Manipulation Language)的简称,主要用于对数据库中的数据进行增加、修改、删除等操作。它是SQL语言的一部分,用于实现对数据库中数…

diffusion model(十五) : IP-Adapter技术小结

infopaperhttps://arxiv.org/pdf/2308.06721.pdfcodehttps://github.com/tencent-ailab/IP-Adapterorg.Tencent AI Lab个人博客地址http://myhz0606.com/article/ip_adapter 1 Motivation 为了对文生图diffusion model进行特定概念的定制,常用LoRA[1]、textual in…

Unity面经(自整)——C#基础

C#基础 1. 重载与重写的区别 封装、继承、多态所处位置不同:重载在同类,重写在父子类中定义方式不同:重载方法名相同参数列表不同,重写方法名和参数列表相同调用方式不同:重载使用相同对象以不同参数调用,重写用不同对象以相同参数调用多态时机不同:重载是编译时多态,…

Android Studio 生成 keystore 签名文件及打包验证流程

一、创建keystore签名文件 1、在菜单栏中,依次点击 Build - Generate Signed Bundle/Apk...(生成签名) 2、选择 APK 选项,点击按钮 Next 到下一步 3、新建key store秘钥文件,点击按钮 Next 到下一步 4、按如下提示填写信息,点击按…

JAVA POI Excel 使用数组公式 FREQUENCY

平台及依赖 JAVA 17POI版本 <dependency><groupId>org.apache.poi</groupId><artifactId>poi</artifactId><version>5.2.5</version></dependency><dependency><groupId>org.apache.poi</groupId><art…

2024-04-08(CSS,JS)

1.标准流 指的是标签在页面中默认的排布规则&#xff0c;例如块元素独占一行&#xff0c;行内元素可以一行显示多个。 2.Flex布局 也称弹性布局&#xff0c;是浏览器提倡的布局模型&#xff0c;适合结构化布局&#xff0c;并且不会产生浮动布局中脱标的现象。 浮动布局&…

1006 换个格式输出整数

让我们用字母 B 来表示“百”、字母 S 表示“十”&#xff0c;用 12...n 来表示不为零的个位数字 n&#xff08;<10&#xff09;&#xff0c;换个格式来输出任一个不超过 3 位的正整数。例如 234 应该被输出为 BBSSS1234&#xff0c;因为它有 2 个“百”、3 个“十”、以及个…

前端潮流速递:从 Electron 到 Tauri —— 构建高性能跨平台桌面应用的新选择

前端潮流速递&#xff1a;从 Electron 到 Tauri —— 构建高性能跨平台桌面应用的新选择 在现代前端开发领域中&#xff0c;Electron 以其便捷的 Web 技术集成和跨平台能力赢得了广泛的认可。然而&#xff0c;随着应用体积不断增大和资源占用问题凸显&#xff0c;寻找更为精简…

【RISC-V 指令集】RISC-V 向量V扩展指令集介绍(四)- 配置和设置指令(vsetvli/vsetivli/vsetvl)

1. 引言 以下是《riscv-v-spec-1.0.pdf》文档的关键内容&#xff1a; 这是一份关于向量扩展的详细技术文档&#xff0c;内容覆盖了向量指令集的多个关键方面&#xff0c;如向量寄存器状态映射、向量指令格式、向量加载和存储操作、向量内存对齐约束、向量内存一致性模型、向量…

蓝桥杯模拟赛练习题—— 燃烧你的卡路里

目标 请在 js/index.js 和 index.html 文件中补全代码&#xff0c;完成以下目标&#xff1a; 点击“定制方案”按钮后&#xff0c;弹出侧滑页面&#xff0c;所使用的组件为 el-drawer&#xff0c;相关属性如下&#xff1a; 参数说明类型默认值v-model是否显示 Drawerboolean…

nginx访问路径映射资源目录

Nginx映射资源目录是指在Nginx配置文件中设定规则&#xff0c;使得当客户端向Nginx服务器发送请求访问某个URL时&#xff0c;Nginx能够将该URL映射到服务器本地的实际文件目录&#xff0c;从而正确地提供该目录下的静态资源&#xff08;如HTML、CSS、JavaScript、图片、视频等文…

短视频技术课程在哪学?来这几个资源网站看看,是你需要的

做短视频&#xff0c;有时候就像是做饭&#xff0c;素材好比是食材&#xff0c;没了好食材&#xff0c;怎么做都是不是味儿。今天&#xff0c;我得跟大家好好聊聊&#xff0c;作为一个剪辑界的“老油条”&#xff0c;我是怎样在这个素材的海洋里捞到宝贝的。九才素材网&#xf…

新手养猫必备!福派斯三文鱼益生菌猫粮,让猫咪更健康

亲爱的朋友们&#xff0c;我知道你们中的许多人可能正在考虑养猫&#xff0c;或者刚刚成为了一位猫奴。对于新手来说&#xff0c;选择合适的猫粮可能是一个令人困惑的问题。今天&#xff0c;我想向大家推荐一款非常适合新人的猫粮——福派斯三文鱼益生菌猫粮。 &#x1f43e; 首…

船气废弃锅炉三维仿真vr交互展示降低培训门槛

火化炉是殡葬行业的核心设备&#xff0c;其操作技艺对于专业人才的培养至关重要。然而&#xff0c;传统实践教学受限于时间、场地、设备损耗等多重因素&#xff0c;难以给予学生充分的实操机会。面对这一挑战&#xff0c;我们创新推出了火化炉vr三维仿真培训软件&#xff0c;以…

程序员的赚钱认知觉醒(下)

13、什么样的事情才值得长久的做&#xff1f;​ ​ 满足以下三个原则的事情才值得我们长久且终身为之奋斗&#xff1a;​ 从自己的专业领域入手​做有积累的事​在鱼多的地方钓鱼​ ​ 在鱼多的地方钓鱼&#xff0c;换句话说是选择一个足够大的赛道&#xff0c;即项目的天花板…

如何在Unity中使用设计模式

在 Unity 环境中,设计模式是游戏开发人员遇到的常见问题的通用解决方案。将它们视为解决游戏开发中特定挑战的经过验证的模板或蓝图。以下是一些简单易懂的设计模式: 1. 单例=> 单例模式确保一个类只有一个实例,并提供对该实例的全局访问点。在 Unity 中,可以使用单例模…

数据挖掘|序列模式挖掘及其算法的python实现

数据挖掘|序列模式挖掘及其算法的python实现 1. 序列模式挖掘2. 基本概念3. 序列模式挖掘实例4. 类Apriori算法&#xff08;GSP算法&#xff09;4.1 算法思想4.2 算法步骤4.3 基于Python的算法实现 1. 序列模式挖掘 序列(sequence)模式挖掘也称为序列分析。 序列模式发现&…

中颖51芯片学习3. 定时器

中颖51芯片学习3. 定时器 一、SH79F9476定时器简介1. 简介2. 定时器运行模式 二、定时器21. 说明&#xff08;1&#xff09;时钟&#xff08;2&#xff09;工作模式 2. 寄存器&#xff08;1&#xff09;控制寄存器 T2CON&#xff08;2&#xff09;定时器2模式控制寄存器 T2MOD …

[大模型]Baichuan2-7B-chat FastApi 部署调用

Baichuan2 介绍 Baichuan 2 是百川智能推出的新一代开源大语言模型&#xff0c;采用 2.6 万亿 Tokens 的高质量语料训练。在多个权威的中文、英文和多语言的通用、领域 benchmark 上取得同尺寸最佳的效果。 环境准备 在autodl平台中租一个3090等24G显存的显卡机器&#xff0c;…