深入理解Java Class文件格式 constant_UTF_info

首先, 让我们回顾一下关于class文件格式的之前两篇博客的主要内容。 在 深入理解Java Class文件格式(一) 中, 讲解了class文件在整个java体系结构中的位置和作用, 讲解了class文件中的魔数和版本号相关的信息, 并且对常量池进行了概述。 在 深入理解Java Class文件格式(二) 中, 主要讲解了class文件中的特殊字符串, 包括类的全限定名, 字段描述符和方法描述符, 这些特殊字符串大量出现在class文件的常量池中, 是理解常量池的基础。 本文会详细讲解常量池中的各个数据项。

如果你还没有读过前两篇文章, 建议先去读一下, 这样才能保持知识的连贯性。 前两篇文章的链接已经在上面给出。 下面开始讲解常量池。

常量池中各数据项类型详解

关于常量池的大概内容, 已经在 深入理解Java Class文件格式(一) 中讲解过了, 这篇文章中还介绍了常量池中的11种数据类型。 本文的任务是详细讲解这11种数据类型, 深度剖析源文件中的各种信息是以什么方式存放在常量池中的。

我们知道, 常量池中的数据项是通过索引来引用的, 常量池中的各个数据项之间也会相互引用。在这11中常量池数据项类型中, 有两种比较基础, 之所以说它们基础, 是因为这两种类型的数据项会被其他类型的数据项引用。 这两种数据类型就是CONSTANT_Utf8 和 CONSTANT_NameAndType , 其中CONSTANT_NameAndType类型的数据项(CONSTANT_NameAndType_info)也会引用CONSTANT_Utf8类型的数据项(CONSTANT_Utf8_info) 。 与其他介绍常量池的书籍或其他资料不同, 本着循序渐进和先后分明的原则, 我们首先对这两种比较基本的类型的数据项进行介绍, 然后再依次介绍其他9中数据项。

(1) CONSTANT_Utf8_info

一个CONSTANT_Utf8_info是一个CONSTANT_Utf8类型的常量池数据项, 它存储的是一个常量字符串。 常量池中的所有字面量几乎都是通过CONSTANT_Utf8_info描述的。下面我们首先讲解CONSTANT_Utf8_info数据项的存储格式。在前面的文章中, 我们提到, 常量池中数据项的类型由一个整型的标志值(tag)决定, 所以所有常量池类型的info中都必须有一个tag信息, 并且这个tag值位于数据项的第一个字节上。 一个11中常量池数据类型, 所以就有11个tag值表示这11中类型。而CONSTANT_Utf8_info的tag值为1, 也就是说如果虚拟机要解析一个常量池数据项, 首先去读这个数据项的第一个字节的tag值, 如果这个tag值为1, 那么就说明这个数据项是一个CONSTANT_Utf8类型的数据项。 紧挨着tag值的两个字节是存储的字符串的长度length, 剩下的字节就存储着字符串。 所以, 它的格式是这样的:

其中tag占一个字节, length占2个字节, bytes代表存储的字符串, 占length字节。所以, 如果这个CONSTANT_Utf8_info存储的是字符串"Hello", 那么他的存储形式是这样的:

现在我们知道了CONSTANT_Utf8_info数据项的存储形式, 那么CONSTANT_Utf8_info数据项都存储了什么字符串呢? CONSTANT_Utf8_info可包括的字符串主要以下这些:

  • 程序中的字符串常量
  • 常量池所在当前类(包括接口和枚举)的全限定名
  • 常量池所在当前类的直接父类的全限定名
  • 常量池所在当前类型所实现或继承的所有接口的全限定名
  • 常量池所在当前类型中所定义的字段的名称和描述符
  • 常量池所在当前类型中所定义的方法的名称和描述符
  • 由当前类所引用的类型的全限定名
  • 由当前类所引用的其他类中的字段的名称和描述符
  • 由当前类所引用的其他类中的方法的名称和描述符
  • 与当前class文件中的属性相关的字符串, 如属性名等

总结一下, 其中有这么五类: 程序中的字符串常量, 类型的全限定名, 方法和字段的名称, 方法和字段的描述符, 属性相关字符串。 程序中的字符串常量不用多说了, 我们经常使用它们创建字符串对象, 属性相关的字符串, 等到讲到class中的属性信息(attibute)时自会提及。 方法和字段的名称也不用多说了 。 剩下的就是类型的全限定名,方法和字段的描述符, 这就是上篇文章中提及的"特殊字符串", 不熟悉的同学可以先读一下上篇文章 深入理解Java Class文件格式(二) 。 还有一点需要说明, 类型的全限定名, 方法和字段的名称, 方法和字段的描述符, 可以是本类型中定义的, 也可能是本类中引用的其他类的。

下面我们通过一个例子来进行说明。 示例源码:

package com.jg.zhang; public class Programer extends Person { 	static String company = "CompanyA";		static{		System.out.println("staitc init");	}			String position;	Computer computer; 	public Programer() {		this.position = "engineer";		this.computer = new Computer();	}		public void working(){		System.out.println("coding...");		computer.working();	}}

别看这个类简单, 但是反编译后, 它的常量池有53项之多。 在这53项常量池数据项中, 各种类型的数据项都有, 当然也包括不少的CONSTANT_Utf8_info 。 下面只列出反编译后常量池中的CONSTANT_Utf8_info 数据项:

#2 = Utf8               com/jg/zhang/Programer          //当前类的全限定名
#4 = Utf8               com/jg/zhang/Person             //父类的全限定名
#5 = Utf8               company                         //company字段的名称
#6 = Utf8               Ljava/lang/String;              //company和position字段的描述符
#7 = Utf8               position                        //position字段的名称
#8 = Utf8               computer                        //computer字段的名称
#9 = Utf8               Lcom/jg/zhang/Computer;         //computer字段的描述符
#10 = Utf8              <clinit>                        //类初始化方法(即静态初始化块)的方法名
#11 = Utf8              ()V                             //working方法的描述符
#12 = Utf8              Code                            //Code属性的属性名
#14 = Utf8              CompanyA                        //程序中的常量字符串
#19 = Utf8              java/lang/System                //所引用的System类的全限定名
#21 = Utf8              out                             //所引用的out字段的字段名
#22 = Utf8              Ljava/io/PrintStream;           //所引用的out字段的描述符
#24 = Utf8              staitc init                     //程序中的常量字符串
#27 = Utf8              java/io/PrintStream             //所引用的PrintStream类的全限定名
#29 = Utf8              println                         //所引用的println方法的方法名
#30 = Utf8              (Ljava/lang/String;)V           //所引用的println方法的描述符
#31 = Utf8              LineNumberTable                 //LineNumberTable属性的属性名
#32 = Utf8              LocalVariableTable              //LocalVariableTable属性的属性名
#33 = Utf8              <init>                          //当前类的构造方法的方法名
#41 = Utf8              com/jg/zhang/Computer           //所引用的Computer类的全限定名
#45 = Utf8              this                            //局部变量this的变量名
#46 = Utf8              Lcom/jg/zhang/Programer;        //局部变量this的描述符
#47 = Utf8              working                         //woking方法的方法名
#49 = Utf8              coding...                       //程序中的字符串常量
#52 = Utf8              SourceFile                      //SourceFile属性的属性名
#53 = Utf8              Programer.java                  //当前类所在的源文件的文件名

上面只列出了反编译结果中常量池中的CONSTANT_Utf8_info数据项。 其中第三列不是javap反编译的输出结果, 而是我加上的注释。 读者可以对比上面的程序源码来看一下, 这样的话, 就可以清楚的看出, 源文件中的各种字符串, 是如何和存放到CONSTANT_Utf8_info中的。

这里要强调一下, 源文件中的几乎所有可见的字符串都存放在CONSTANT_Utf8_info中, 其他类型的常量池项只不过是对CONSTANT_Utf8_info的引用。 其他常量池项, 把引用的CONSTANT_Utf8_info组合起来, 进而可以描述更多的信息。 下面将要介绍的CONSTANT_NameAndType_info就可以验证这个结论。

(2) CONSTANT_NameAndType类型的数据项

常量池中的一个CONSTANT_NameAndType_info数据项, 可以看做CONSTANT_NameAndType类型的一个实例 。 从这个数据项的名称可以看出, 它描述了两种信息,第一种信息是名称(Name), 第二种信息是类型(Type) 。 这里的名称是指方法的名称或者字段的名称, 而Type是广义上的类型, 它其实描述的是字段的描述符或方法的描述符。 也就是说, 如果Name部分是一个字段名称, 那么Type部分就是相应字段的描述符; 如果Name部分描述的是一个方法的名称, 那么Type部分就是对应的方法的描述符。 也就是说, 一个CONSTANT_NameAndType_info就表示了一个方法或一个字段。

下面先看一下CONSTANT_NameAndType_info数据项的存储格式。 既然是常量池中的一种数据项类型, 那么它的第一个字节也是tag, 它的tag值是12, 也就是说, 当虚拟机读到一个tag为12的常量池数据项, 就可以确定这个数据项是一个CONSTANT_NameAndType_info 。 tag值一下的两个字节叫做name_index, 它指向常量池中的一个CONSTANT_Utf8_info, 这个CONSTANT_Utf8_info中存储的就是方法或字段的名称。 name_index以后的两个字节叫做descriptor_index, 它指向常量池中的一个CONSTANT_Utf8_info, 这个CONSTANT_Utf8_info中存储的就是方法或字段的描述符。 下图表示它的存储布局:

下面举一个实例进行说明, 实例的源码为:

package com.jg.zhang; public class Person { 	int age; 	int getAge(){		return age;	}}

这个Person类很简单, 只有一个字段age, 和一个方法getAge 。 将这段代码使用javap工具反编译之后, 常量池信息如下:

   #1 = Class              #2             //  com/jg/zhang/Person
   #2 = Utf8               com/jg/zhang/Person
   #3 = Class              #4             //  java/lang/Object
   #4 = Utf8               java/lang/Object
   #5 = Utf8               age
   #6 = Utf8               I
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Methodref          #3.#11         //  java/lang/Object."<init>":()V
  #11 = NameAndType        #7:#8          //  "<init>":()V
  #12 = Utf8               LineNumberTable
  #13 = Utf8               LocalVariableTable
  #14 = Utf8               this
  #15 = Utf8               Lcom/jg/zhang/Person;
  #16 = Utf8               getAge
  #17 = Utf8               ()I
  #18 = Fieldref           #1.#19         //  com/jg/zhang/Person.age:I
  #19 = NameAndType        #5:#6          //  age:I
  #20 = Utf8               SourceFile
  #21 = Utf8               Person.java

常量池一共有21项, 我们可以看到, 一共有两个CONSTANT_NameAndType_info 数据项, 分别是第#11项和第#19项, 其中第#11项的CONSTANT_NameAndType_info又引用了常量池中的第#7项和第#8项, 被引用的这两项都是CONSTANT_Utf8_info , 它们中存储的字符串常量值分别是 和 ()V。 其实他们加起来表示的就是父类Object的构造方法。 那么这里为什么会是父类Object的构造方法而不是本类的构造方法呢? 这是因为类中定义的方法如果不被引用(也就是说在当前类中不被调用), 那么常量池中是不会有相应的 CONSTANT_NameAndType_info 与之对应的, 只有引用了一个方法, 才有相应的CONSTANT_NameAndType_info 与之对应。 这也是为什么说CONSTANT_NameAndType_info 是方法的符号引用的一部分的原因。 (这里提到一个新的概念, 叫做方法的符号引用, 这个概念会在后面的博客中进行讲解) 可以看到, 在源码存在两个方法, 分别是编译器默认添加的构造方法和我们自己定义的getAge方法, 因为并没有在源码中显示的调用这两个方法,所以在常量池中并不存在和这两个方法相对应的CONSTANT_NameAndType_info 。 之所以会存在父类Object的构造方法对应的CONSTANT_NameAndType_info , 是因为子类构造方法中会默认调用父类的无参数构造方法。 我们将常量中的其他信息去掉, 可以看得更直观:

下面讲解常量池第#19项的CONSTANT_NameAndType_info , 它引用了常量池第#5项和第#6项, 这两项也是CONSTANT_Utf8_info 项, 其中存储的字符串分别是age和I, 其中age是源码中字段age的字段名, I是age字段的描述符。 所以这个CONSTANT_NameAndType_info 就表示对本类中的字段age的引用。 除去常量池中的其他信息, 可以看得更直观:

和方法相同, 只定义一个字段而不引用它(在源码中表现为不访问这个变量), 那么在常量池中也不会存在和该字段相对应的CONSTANT_NameAndType_info 项。这也是为什么说CONSTANT_NameAndType_info作为字段符号引用的一部分的原因。 (这里提到一个新的概念, 叫做字段的符号引用, 这个概念会在后面的博客中进行讲解) 在本例中之所以会出现这个CONSTANT_NameAndType_info , 是因为在源码的getAge方法中访问了这个字段:

	int getAge(){		return age;	}

下面给出这两个CONSTANT_NameAndType_info真实的内存布局图:

和Object构造方法相关的CONSTANT_NameAndType_info的示意图:

和age字段相关的CONSTANT_NameAndType_info示意图:

这两张图能够很好的反映出CONSTANT_NameAndType_info和CONSTANT_Utf8_info 这两种常量池数据项的数据存储方式, 也能够真实的反应CONSTANT_NameAndType_info和CONSTANT_Utf8_info 的引用关系。

总结

本篇博客就到此为止, 在本文中我们主要介绍了常量池中的两种数据项: CONSTANT_NameAndType_info 和 CONSTANT_Utf8_info 。 其中CONSTANT_Utf8_info存储的是源文件中的各种字符串, 而CONSTANT_NameAndType_info表述的是源文件中对一个字段或方法的符号引用的一部分(即 方法名加方法描述符, 或者是 字段名加字段描述符)。在下一篇博客中, 继续讲解常量池中的其他类型的数据项 。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Postman+Java springboot演示 get post put delete请求并携带(路径 路径问号后 json 表单)参数形式

我们先创建一个java的springboot工程 在项目中 找到启动类的位置目录 在项目创建一个类 叫 user 我是想将 user 当做一个属性类的 按规范来讲 我们可以创建一个entity包 然后在下面去创建属性类 但这里 我们不想搞那么麻烦了 毕竟只是练习一下 然后 user参考代码如下 package…

Django框架004:orm对mysql的增删改查

大家好&#xff0c;我是csdn的博主&#xff1a;lqj_本人 这是我的个人博客主页&#xff1a; lqj_本人的博客_CSDN博客-微信小程序,前端,python领域博主lqj_本人擅长微信小程序,前端,python,等方面的知识https://blog.csdn.net/lbcyllqj?spm1011.2415.3001.5343哔哩哔哩欢迎关注…

力扣---LeetCode141/142. 环形链表 (I)和(II) (代码详解+流程图+数学逻辑拓展)

文章目录 前言141. 环形链表 I1.1 链接&#xff1a;1.2 思路&#xff1a;1.3 代码&#xff1a;快慢指针1.4 流程图&#xff1a; 142. 环形链表 II2.1 链接&#xff1a;2.2 思路&#xff1a;2.3 代码&#xff1a;2.4 流程图&#xff1a; 拓展问题及证明(面试常问)&#xff1a;3.…

线上问题-CPU使用频率飙升

描述 中午收到群内人员反馈环境访问速度慢。登录验证码打不开等问题。通过查看日志发现是kafka出现问题&#xff0c;无法处理消息。联系运维解决。在排查的过程中使用mobaXterm连接服务器。左下角看到CPU使用频率非常高。于是记录一下通过CPU查看程序占用情况分析问题。 过程 …

Ansys Lumerical | CMOS - 光学仿真方法

通过使用更小的像素尺寸和更大的填充因子&#xff0c;基于CMOS图像传感器像素的数码相机系统的成本正在降低。但是&#xff0c;只有在不牺牲图像质量的情况下&#xff0c;CMOS像素尺寸减小才是可以接受的。随着CMOS像素尺寸的不断减小&#xff0c;图像信噪比降低&#xff0c;相…

2.1 Linux命令行

系列文章目录 第1章 Linux Shell简介 第2章 Shell基础 <本章所在位置> 第3章 Bash Shell基础命令 第4章 Bash Shell命令进阶 第5章 Linux Shell深度理解 第6章 Linux环境变量 第7章 Linux文件权限 第8章 Linux文件系统的管理 第9章 Linux软件安装 第10章 Linux文本编辑器…

记录一次docker容器引起的时间相差8h的问题

一、背景 系统打印日志时间小8h&#xff0c;部分插入mysql的日期却大8h&#xff0c;简直诡异。 测试时间是上午10:05 经过排查&#xff0c;mysql设置的时区&#xff0c;链接url设置的时区都是ok的。而且有其他服务时间正常&#xff0c;故排除MySQL的问题。 二、排查 2.1 查…

聚焦丨酷雷曼荣列XRMA联盟成员单位

自“元宇宙”概念兴起之初&#xff0c;酷雷曼VR所属北京同创蓝天云科技有限公司就积极布局、探索和实践。2022年12月&#xff0c;酷雷曼VR成功加入虚拟现实与元宇宙产业联盟&#xff08;XRMA&#xff09;&#xff0c;正式被接纳为联盟成员单位&#xff0c;意味着酷雷曼公司将进…

【电动车】基于双层凸优化的燃料电池混合动力汽车研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️❤️&#x1f4a5;&#x1f4a5;&#x1f4a5;&#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清…

rosbag相关进阶操作

一些很好用的网站 时间戳在线转换网页 旋转矩阵、四元数、绕轴旋转、欧拉角在线转换网页 四元数、欧拉角可视化在线转换网页 一、按时间截取bag 使用如下代码&#xff1a; rosbag filter 原始包名.bag 截取后的包名.bag "t.to_sec() > 开始时间 and t.to_sec() <…

初识Elasticsearch

初识Elasticsearch Elasticsearch 是一个分布式&#xff0c;RESTful 风格的搜索和数据分析引擎。它能够提供实时的搜索与数据分析功能&#xff0c;且能够将几乎所有类型的数据存储和搜索&#xff0c;包括结构化和非结构化数据。 在该博文中&#xff0c;我们将介绍 Elasticsea…

在CentOS上安装Jenkins并配置Docker

文章目录 步骤1 - 安装Java 11步骤2 - 安装Jenkins步骤3 - 安装Docker步骤4 - 配置Docker Cloud步骤 5 - 验证步骤 6 - 可能会遇到的问题 在本教程中&#xff0c;我们将展示如何在CentOS上安装Jenkins和Docker&#xff0c;并将它们配置在同一台机器上&#xff0c;使Jenkins能够…

开发必备,开源 or 免费的 AI 编程助手

AI 大模型的火热&#xff0c;让开发圈近来如虎添翼&#xff0c;各种各样基于 AI 技术的开发者工具和新范式不断涌现&#xff0c;尤其是 Github 和 OpenAI 共同推出的 Copilot X &#xff0c;更是一骑绝尘。本文推荐一些开源 or 免费的 AI 编程工具&#xff0c;不妨试着用起来。…

当今自然语言处理领域中的成功之路:Transformer模型

当今自然语言处理领域中最重要和最成功的模型之一是Transformer模型。它是一种基于自注意力机制的神经网络模型&#xff0c;最初由Google公司的研究人员提出&#xff0c;并被广泛应用于机器翻译、文本生成、情感分析等任务中。 Transformer模型之所以被广泛使用&#xff0c;是因…

边缘化你必须知道的一件事!(FEJ知识点总结)

vins和g2o边缘化的异同&#xff1a;(已经做到ppt里面了&#xff0c;简单回顾一下) 1.《视觉slam14讲》中提及的边缘化(G2O边缘化)是在计算求解过程中&#xff0c;先消去路标点变量&#xff0c;实现先求解相机位姿&#xff0c;然后再利用求解出来的相机位姿反过来计算路标点的过…

springboot第七章 结合Dubbo

实现Dubbo分布式框架&#xff0c;需要公共接口maven项目&#xff0c;需要服务提供者springboot项目&#xff0c;需要服务消费者springboot项目。 因为公共接口只有数据类和接口&#xff0c;后期提供者和消费者需要根据maven唯一坐标来导入公共接口项目的jar包&#xff0c;因此公…

GraphHopper调研笔记

一、 GraphHopper GraphHopper是一种快速且内存有效的Java导航引擎&#xff0c;默认使用OSM和GTFS数据&#xff0c;也可导入其他的数据源。支持CH&#xff08;Contraction Hierarchies&#xff09;、A*、Dijkstra算法。 1、应用介绍 graphhopper有以下几种常见的地图应用&am…

25000 字详解 23 种设计模式(多图 + 代码)

25000 字详解 23 种设计模式&#xff08;多图 代码&#xff09; 目录 创建型模式结构型模式行为型模式总结 前言 一直想写一篇介绍设计模式的文章&#xff0c;让读者可以很快看完&#xff0c;而且一看就懂&#xff0c;看懂就会用&#xff0c;同时不会将各个模式搞混。 设计…

前端项目的通用优化策略

一、虚拟滚动 当我们开发的时候&#xff0c;遇到大数据加载&#xff0c;页面卡顿的问题应该如何处理&#xff1f;大多数情况下&#xff0c;我们都是尽量通过分页的方式处理这类问题&#xff0c;但是总有一些特殊的情况我们必须把数据全部加载到前端进行处理。我曾经遇到过一个…

MySQL入门

创建数据库 用CREATE DATABASE关键字&#xff08;也可以小写但建议关键字用大写方便区分&#xff09;创建一个名为“mydatabase”的数据库。 CREATE DATABASE mydatabase; 如果名称和关键字相撞&#xff0c;可以用Esc键下面的反引号括起来&#xff08;关键字会显示蓝色&#…