深入理解Java虚拟机:JVM高级特性与最佳实践-总结-9

news2025/1/16 18:04:27

深入理解Java虚拟机:JVM高级特性与最佳实践-总结-9

  • 虚拟机类加载机制
    • 类加载的过程
      • 准备
      • 解析
          • 字段解析
      • 方法解析
      • 接口方法解析

虚拟机类加载机制

类加载的过程

准备

准备阶段是正式为类中定义的变量(即静态变量,被static修饰的变量)分配内存并设置类变量初始值的阶段,从概念上讲,这些变量所使用的内存都应当在方法区中进行分配,但必须注意到方法区本身是一个逻辑上的区域,在JDK 7及之前,HotSpot使用永久代来实现方法区时,实现是完全符合这种逻辑概念的;而在JDK 8及之后,类变量则会随着Class对象一起存放在Java堆中,这时候“类变量在方法区”就完全是一种对逻辑概念的表述了。

准备阶段进行内存分配的仅包括类变量,而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在Java堆中。 这里所说的初始值“通常情况”下是数据类型的零值,假设一个类变量的定义为:

public static int value = 123;

变量value在准备阶段过后的初始值为0而不是123,因为这时尚未开始执行任何Java方法,而把value赋值为123的putstatic指令是程序被编译后,存放于类构造器<clinit>()方法之中,所以value赋值为123的动作要到类的初始化阶段才会被执行。 下表列出了Java中所有基本数据类型的零值:
在这里插入图片描述
在“通常情况”下初始值是零值,某些“特殊情况”:如果类字段的字段属性表中存在ConstantValue属性,那在准备阶段变量值就会被初始化为ConstantValue属性所指定的初始值,假设上面类变量value的定义修改为:

public static final int value = 123;

编译时Javac将会为value生成ConstantValue属性,在准备阶段虚拟机就会根据ConstantValue的设置将value赋值为123。

解析

解析阶段是Java虚拟机将常量池内的符号引用替换为直接引用的过程,在Class文件中符号引用以CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info等类型的常量出现。

  • 符号引用(Symbolic References):符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。符号引用与虚拟机实现的内存布局无关,引用的目标并不一定是已经加载到虚拟机内存当中的内容。各种虚拟机实现的内存布局可以各不相同,但是它们能接受的符号引用必须都是一致的,因为符号引用的字面量形式明确定义在《Java虚拟机规 范》的Class文件格式中。
  • 直接引用(Direct References):直接引用是可以直接指向目标的指针、相对偏移量或者是一个能间接定位到目标的句柄。直接引用是和虚拟机实现的内存布局直接相关的,同一个符号引用在不同虚拟机实例上翻译出来的直接引用一般不会相同。如果有了直接引用,那引用的目标必定已经在虚拟机的内存中存在。

对方法或者字段的访问,也会在解析阶段中对它们的可访问性(public、protected、private、<package>)进行检查。

对同一个符号引用进行多次解析请求是很常见的事情,除invokedynamic指令以外,虚拟机实现可以对第一次解析的结果进行缓存,譬如在运行时直接引用常量池中的记录,并把常量标识为已解析状态,从而避免解析动作重复进行。无论是否真正执行了多次解析动作,Java虚拟机都需要保证的是在同一个实体中,如果一个符号引用之前已经被成功解析过,那么后续的引用解析请求就应当一直能够成功;同样地,如果第一次解析失败了,其他指令对这个符号的解析请求也应该收到相同的异常,哪怕这个请求的符号在后来已成功加载进Java虚拟机内存之中。

不过对于invokedynamic指令,上面的规则就不成立了。当碰到某个前面已经由invokedynamic指令触发过解析的符号引用时,并不意味着这个解析结果对于其他invokedynamic指令也同样生效。因为invokedynamic指令的目的本来就是用于动态语言支持,它对应的引用称为“动态调用点限定符(Dynamically-Computed Call Site Specifier)”,这里“动态”的含义是指必须等到程序实际运行到这条指令时,解析动作才能进行。相对地,其余可触发解析的指令都是“静态”的,可以在刚刚完成加载阶段,还没有开始执行代码时就提前进行解析。

解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符这7类符号引用进行,分别对应于常量池的CONSTANT_Class_info、CON-STANT_Fieldref_info、CONSTANT_Methodref_info、CONSTANT_InterfaceMethodref_info、CONSTANT_MethodType_info、CONSTANT_MethodHandle_info、CONSTANT_Dyna-mic_info和CONSTANT_InvokeDynamic_info 8种常量类型

1.类或接口的解析
假设当前代码所处的类为D,如果要把一个从未解析过的符号引用N解析为一个类或接口C的直接引用,那虚拟机完成整个解析的过程需要包括以下3个步骤:

  1. 如果C不是一个数组类型,那虚拟机将会把代表N的全限定名传递给D的类加载器去加载这个类C。在加载过程中,由于元数据验证、字节码验证的需要,又可能触发其他相关类的加载动作,例如加载这个类的父类或实现的接口。一旦这个加载过程出现了任何异常,解析过程就将宣告失败。
  2. 如果C是一个数组类型,并且数组的元素类型为对象,也就是N的描述符会是类似“[Ljava/lang/Integer”的形式,那将会按照第一点的规则加载数组元素类型。如果N的描述符如前面所假设的形式,需要加载的元素类型就是“java.lang.Integer”,接着由虚拟机生成一个代表该数组维度和元素的数组对象。
  3. 如果上面两步没有出现任何异常,那么C在虚拟机中实际上已经成为一个有效的类或接口了,但在解析完成前还要进行符号引用验证,确认D是否具备对C的访问权限。如果发现不具备访问权限,将抛出java.lang.IllegalAccessError异常。

针对上面第3点访问权限验证,在JDK 9引入了模块化以后,一个public类型也不再意味着程序任何位置都有它的访问权限,我们还必须检查模块间的访问权限。
如果我们说一个D拥有C的访问权限,那就意味着以下3条规则中至少有其中一条成立:

  • 被访问类C是public的,并且与访问类D处于同一个模块。
  • 被访问类C是public的,不与访问类D处于同一个模块,但是被访问类C的模块允许被访问类D的 模块进行访问。
  • 被访问类C不是public的,但是它与访问类D处于同一个包中。

在后续涉及可访问性时,都必须考虑模块间访问权限隔离的约束,即以上列举的3条规则。

字段解析

要解析一个未被解析过的字段符号引用,首先将会对字段表内class_index项中索引的CONSTANT_Class_info符号引用进行解析,也就是字段所属的类或接口的符号引用。如果在解析这个类或接口符号引用的过程中出现了任何异常,都会导致字段符号引用解析的失败。如果解析成功完
成,那把这个字段所属的类或接口用C表示,《Java虚拟机规范》要求按照如下步骤对C进行后续字段
的搜索:

  1. 如果C本身就包含了简单名称和字段描述符都与目标相匹配的字段,则返回这个字段的直接引 用,查找结束。
  2. 否则,如果在C中实现了接口,将会按照继承关系从下往上递归搜索各个接口和它的父接口,如果接口中包含了简单名称和字段描述符都与目标相匹配的字段,则返回这个字段的直接引用,查找结束。
  3. 否则,如果C不是java.lang.Object的话,将会按照继承关系从下往上递归搜索其父类,如果在父类中包含了简单名称和字段描述符都与目标相匹配的字段,则返回这个字段的直接引用,查找结束。
  4. 否则,查找失败,抛出java.lang.NoSuchFieldError异常。

如果查找过程成功返回了引用,将会对这个字段进行权限验证,如果发现不具备对字段的访问权限,将抛出java.lang.IllegalAccessError异常。

以上解析规则能够确保Java虚拟机获得字段唯一的解析结果,但在实际情况中,Javac编译器往往
会采取比上述规范更加严格一些的约束,譬如有一个同名字段同时出现在某个类的接口和父类当中,
或者同时在自己或父类的多个接口中出现,按照解析规则仍是可以确定唯一的访问字段,但Javac编译
器就可能直接拒绝其编译为Class文件。下列代码清单演示了这种情况,如果注释了Sub类中的“public static int A=4;”,接口与父类同时存在字段A,那Oracle公司实现的Javac编译器将提示“The field Sub.A is ambiguous”,并且会拒绝编译这段代码。

public class FieldResolution {
   
   interface Interface0 {
      int A = 0;
   }
   
   interface Interface1 extends Interface0 {
      int A = 1;
   }
   interface Interface2 {
      int A = 2;
   }

static class Parent implements Interface1 {
   public static int A = 3;
}

static class Sub extends Parent implements Interface2 {
   public static int A = 4;
}

   public static void main(String[] args) {
      System.out.println(Sub.A);
   }
}

方法解析

方法解析的第一个步骤与字段解析一样,也是需要先解析出方法表的class_index项中索引的方法所属的类或接口的符号引用,如果解析成功,那么我们依然用C表示这个类,接下来虚拟机将会按照如下步骤进行后续的方法搜索:

  1. 由于Class文件格式中类的方法和接口的方法符号引用的常量类型定义是分开的,如果在类的方法表中发现class_index中索引的C是个接口的话,那就直接抛出java.lang.IncompatibleClassChangeError异常。
  2. 如果通过了第一步,在类C中查找是否有简单名称和描述符都与目标相匹配的方法,如果有则返回这个方法的直接引用,查找结束。
  3. 否则,在类C的父类中递归查找是否有简单名称和描述符都与目标相匹配的方法,如果有则返回这个方法的直接引用,查找结束。
  4. 否则,在类C实现的接口列表及它们的父接口之中递归查找是否有简单名称和描述符都与目标相匹配的方法,如果存在匹配的方法,说明类C是一个抽象类,这时候查找结束,抛java.lang.AbstractMethodError异常。
  5. 否则,宣告方法查找失败,抛出java.lang.NoSuchMethodError

最后,如果查找过程成功返回了直接引用,将会对这个方法进行权限验证,如果发现不具备对此
方法的访问权限,将抛出java.lang.IllegalAccessError异常。

接口方法解析

接口方法也是需要先解析出接口方法表的class_index项中索引的方法所属的类或接口的符号引用,如果解析成功,依然用C表示这个接口,接下来虚拟机将会按照如下步骤进行后续的接口方法搜索:

  1. 与类的方法解析相反,如果在接口方法表中发现class_index中的索引C是个类而不是接口,那
    么就直接抛出java.lang.IncompatibleClassChangeError异常。
  2. 否则,在接口C中查找是否有简单名称和描述符都与目标相匹配的方法,如果有则返回这个方法的直接引用,查找结束。
  3. 否则,在接口C的父接口中递归查找,直到java.lang.Object类(接口方法的查找范围也会包括
    Object类中的方法)为止,看是否有简单名称和描述符都与目标相匹配的方法,如果有则返回这个方法的直接引用,查找结束。
  4. 对于规则3,由于Java的接口允许多重继承,如果C的不同父接口中存有多个简单名称和描述符都与目标相匹配的方法,那将会从这多个方法中返回其中一个并结束查找,《Java虚拟机规范》中并没有进一步规则约束应该返回哪一个接口方法。但与之前字段查找类似地,不同发行商实现的Javac编译器有可能会按照更严格的约束拒绝编译这种代码来避免不确定性。
  5. 否则,宣告方法查找失败,抛出java.lang.NoSuchMethodError异常。

JDK 9之前,Java接口中的所有方法都默认是public的,也没有模块化的访问约束,所以不存在访问权限的问题,接口方法的符号解析就不可能抛出java.lang.IllegalAccessError异常。但在JDK 9中增加了接口的静态私有方法,也有了模块化的访问约束,所以从JDK 9起,接口方法的访问也完全有可能因访问权限控制而出现java.lang.IllegalAccessError异常。

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

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

相关文章

检测字符串中所有的字母是否都为大写(字符中的数字、符号和空格不起作用)isupper()

【小白从小学Python、C、Java】 【计算机等考500强证书考研】 【Python-数据分析】 检测字符串中所有的字母是否都为大写 &#xff08;字符中的数字、符号和空格不起作用&#xff09; isupper() 选择题 以下程序的运行结果是? print("【执行】print(PYTHON.isupper())&qu…

chatgpt赋能Python-python_endif

Python Endif: 程序员必备的关键字 如果你是一位有经验的Python程序员&#xff0c;那么你一定非常熟悉Python中的一个关键字&#xff1a;Endif。本文将深入介绍Endif&#xff0c;帮助程序员更好地理解其作用和用法。 什么是Endif Endif是Python中的一个关键字&#xff0c;它…

【自然语言处理】- 作业5: 智能问答在法律智能领域的应用

课程链接: 清华大学驭风计划 代码仓库&#xff1a;Victor94-king/MachineLearning: MachineLearning basic introduction (github.com) 驭风计划是由清华大学老师教授的&#xff0c;其分为四门课&#xff0c;包括: 机器学习(张敏教授) &#xff0c; 深度学习(胡晓林教授), 计算…

Java如何连接数据库

Java连接MySQL数据库的方法&#xff1a;首先下载解压得到jar库文件&#xff0c;并在对应的项目中导入该库文件;然后添加JDBC;接着在Mysql数据库中进行建表&#xff0c;和添加数据的操作;最后连接数据库并读取数据即可。 Java 连接 MySQL数据库需要驱动包&#xff0c;解压后得到…

springboot国际化多语言配置

文章目录 概要springboot项目为例1 新建路径/文件2 新建两个配置类 搞一个控制器测试总结 概要 项目中有时候会用到多语言的业务场景; 一般来说都是通过后端实现的,将先有内容替换为适用的环境语言; springboot项目为例 1 新建路径/文件 新建路径static/i18n新建文件: mess…

【数据结构与算法】- 周测二

课程链接: 清华大学驭风计划 代码仓库&#xff1a;Victor94-king/MachineLearning: MachineLearning basic introduction (github.com) 驭风计划是由清华大学老师教授的&#xff0c;其分为四门课&#xff0c;包括: 机器学习(张敏教授) &#xff0c; 深度学习(胡晓林教授), 计算…

简单易懂:Ajax入门实例详解(登录功能)

前言&#xff1a;不积跬步无以至千里&#xff0c;不积小流无以成江河&#xff01; 废话不多&#xff0c;以最简练的语言和实例初步了解Ajax&#xff01; 一、Ajax简介 Ajax&#xff08;Asynchronous JavaScript and XML&#xff09;是一种基于Web技术的编程实现方式&#xff0c…

【CCIG技术论坛回顾】展望AI时代,把握文档图像智能分析与处理的未来

展望AI时代&#xff0c;把握文档图像智能分析与处理的未来 前言 CCIG技术论坛 内容回顾及探讨一、人工智能大模型时代的文档识别与理解1.1 文档分析与识别 介绍1.2 文档识别历史回顾1.3 文档的种类与研究问题1.4 文档识别与理解研究现状1.5 大模型带来的挑战与机遇1.5.1 ChatGP…

chrome和Chromedriver版本不一致的问题,然后就要下载对应版本的chromedriver。

很多人会遇到chrome和Chromedriver版本不一致的问题&#xff0c;然后就要下载对应版本的chromedriver。 下面说一下我遇到的问题和解决过程&#xff1a; 1、问题&#xff1a; 我是用pythonselenium 我的chrome版本是65.0.3325.181&#xff0c;用selenium中的webdriver时&am…

案例16:Java音乐网站系统设计与实现开题报告

博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; java项目精品实战案例《100套》 Jav…

MySQL-多表查询(中)

♥️作者&#xff1a;小刘在C站 ♥️个人主页&#xff1a;小刘主页 ♥️每天分享云计算网络运维课堂笔记&#xff0c;努力不一定有收获&#xff0c;但一定会有收获加油&#xff01;一起努力&#xff0c;共赴美好人生&#xff01; ♥️树高千尺&#xff0c;落叶归根人生不易&…

Linux指令速查

文章目录 Linux指令速查1.Linux初步认识1.1 Linux标识符1.2 Linux目录结构1.2.1 (/)目录结构说明1.2.2 目录颜色说明 1. 帮助命令1.1 help指令1.2 man指令 2. 快捷操作3. 文件或目录的管理3.1 处理目录的基本命令3.1.1 列出目录&#xff08;ls&#xff09;3.1.2 切换目录&#…

怎么申请免费的cdn?带附件图文详细操作

背景 我的服务器在国外&#xff0c;域名国内正规备案&#xff0c;但由于国外服务器到国内实在太慢&#xff0c;所以用了cdn&#xff0c;先是用cloudflare&#xff0c;结果慢的惊人&#xff0c;本来测速需要12s&#xff0c;加上cloudflare之后需要15s以上。。。 测速的网站是这…

chatgpt赋能Python-python_dingtalk

Python DingTalk&#xff1a;高效企业通讯利器 当今互联网时代&#xff0c;企业内部沟通必不可少。在成千上万的企业应用中&#xff0c;DingTalk作为高效沟通工具被越来越多的企业所使用。Python DingTalk库的出现&#xff0c;为企业带来了更加方便快捷的通讯方式。 什么是Py…

【数据结构】图的定义,存储,遍历

&#x1f38a;专栏【数据结构】 &#x1f354;喜欢的诗句&#xff1a;更喜岷山千里雪 三军过后尽开颜。 &#x1f386;音乐分享【Dream It Possible】 大一同学小吉&#xff0c;欢迎并且感谢大家指出我的问题&#x1f970; 目录 &#x1f354;前言 &#x1f381;图的定义 &a…

安全中级2:nginx的中间件漏洞

目录 一、nginx解析php的流程 1.原理 2.CGI、FastCGI、PHP-FPM、PHP-CG、WrapperI的定义 二、Fastcgi协议 1.Fastecgi Record 2.Fastcgi Type 3.PHP-FPM(FastCGI进程管理器) 4.总结FastCGI解析的流程 三、nginx配置错误导致的漏洞 1.CRLF注入漏洞&#xff08;$uri解…

rk3568 适配rk809音频

rk3568 适配rk809音频 RK809是一款集成了多种功能的电源管理芯片&#xff0c;主要用于笔记本电脑、平板电脑、工控机等设备的电源管理。以下是RK809的详细功能介绍&#xff1a; 电源管理&#xff1a;控制电源的开关、电压、电流等参数&#xff0c;保证设备的稳定运行。音频管…

开放原子训练营(第三季)inBuilder低代码开发实验室之人人都是开发者

目录 前言: 一、知己知彼 1.1全场景 全角色 多终端 快速开发 1.2直觉化 响应式 1.3所见即所得 1.4全栈开发 二、百战百胜 2.1inBuilder开发入门 2.2点火启动&#xff0c;新的征程 三、实战表现 3.1实战总结 3.2实战效果 四、总结 前言: 2023年&#xff0c;是我们摘…

chatgpt赋能Python-python_embedded_null_character

Python中嵌入空字符的介绍 在Python编程语言中&#xff0c;空字符是一种特殊的字符&#xff0c;它在字符串中表示为空格或空行。不过&#xff0c;在某些情况下添加一个空字符是非常必要的&#xff0c;例如在编码时需要将字符串以空字符分隔&#xff0c;或者在数据库存储中需要…

CC++——深入探究动态内存管理

文章目录 总述C&C程序内存区域划分C内存管理方式运用new/delete操作内置类型new和delete操作自定义类型 operator new与operator delete函数new和delete操作符的实现原理自定义类型 malloc/free 和 new/delete 的区别 总述 俗话说&#xff0c;没有理解过底层的c&c程序员…