Java Class 类文件格式看这一篇就够了

本文将揭开Java Class文件的神秘面纱,带你了解Class文件的内部结构,并从Class文件结构的视角告诉你:

  • 为什么Java Class字节码文件可以“写一次,遍地跑”?
  • 为什么常量池的计数从1开始,而不是和java等绝大多数语言的习惯一样从0开始计数?
  • 为什么在Java应用运行期间,无法使用反射在普通对象中获取到泛型信息?

平台无关性

Java应用之所以能“Write once, Run anywhere”,是因为有JVM虚拟机这个中间媒介来执行Java程序,而JVM虚拟机不和包括Java在内的任何语言绑定,它只和“Class”文件这种约定的二进制文件格式所关联。虚拟机通过载入和执行平台无关的字节码,从而实现了程序的“Write once, Run anywhere”。

什么是Class文件?

“深入理解Java虚拟机”一书中给出了定义,“Class文件是一组以8位字节为基础单位的二进制流”。各个数据项目按照顺序紧凑排列,中间没有分隔符,整个Class文件没有一点空间上的浪费。

利用idea插件BinEd打开Class文件,我们可以看到用十六进制表示的Class文件,开头是固定的0xCAFEBABE(咖啡宝贝)魔数,它的唯一作用是用来验证此文件是可以被虚拟机接受的Class文件,而不是通过后缀.class来验证,因为后缀名是可以人为修改的。很多格式如gif或者jpeg等文件头都存在魔数。

Class文件格式

Class文件格式按照虚拟机规范的约定,采用一种类似于C语言结构体的伪结构来存储数据,只要各个平台编译器能够严格遵守Java虚拟机的规范来生成Class文件,Java虚拟机就能生成它可执行的Class文件。在Class文件中只有两种数据类型:

  • 无符号数。属于基本的数据类型,u1、u2、u4和u8分别表示1个、2个、4个和8个字节的无符号数,它们可以用来描述数字、索引引用、数量值或者utf-8编码的字符串。
  • 表。是由多个无符号数或其他表构成的复合数据结构。表习惯以“_info”结尾,整个Class文件本质上也是一张表,因为它也是具有层次关系的复合数据类型。

如上图,当同一个数据类型有多个时,经常会在这个数据类型集合的前面加上一个计数器。例如,fields数据项表示Class文件里的多个field_info字段,其前面的fields_count保存了fields集合的数量。

下面正式开始介绍Class文件的各个数据项的含义和作用。

Class文件的版本

前面已经介绍了Class文件以魔数(magic)开头,魔数是u4类型,占用了4个字节。紧随其后的第5和第6个字节是次版本号(Minor Version),第7和第8个字节是主版本号(Major Version)。Java的版本号从45开始,从1.1开始每个JDK大版本的主版本号都向上加1(JDK 1.0~1.1使用了45.0~45.3的版本号)。例如,我的本地JDK版本是1.8,所以编译的Class文件大版本号是十六进制0x0034,即十进制52。

版本号的作用是保证高版本的JDK向下兼容低版本的Class文件,但必须拒绝超过其版本号的Class文件。

常量池

主版本号之后是常量池入口,常量池是占用Class文件空间最大的数据项目之一,因为其他项目里存放的引用类型,是和常量池里存放的数据进行的关联。例如,类索引(this_class)是u2类型的数据,里面存放的是引用常量池的地址,常量池对应的区域保存的是类的全限定名。

我们先用一个示例来大致的了解一下常量池的结构。以下是class文件对应的源代码,我们可以使用idea插件jclasslib,打开这个类的class文件看看它的构造。

public class GuoClass<T> {private int money;public int make() {return money + 1000000000;}
}

在一般信息里,我们看到有一项“本类索引”(this_class),cp_info表示本类索引的数据类型是常量池类型(constant_pool_info),编号#4表示本类索引指向的是常量池的4号位置,它存放的数据是CONSTANT_Class_info类型。

点开常量池编号#4的索引,可以看到它存放的也是一个cp_info的数据,显然我们可以再次点击它跳转到常量池编号25的位置看看。

果然,这里我们看到了常量池编号#25的位置存放的是一个字符串字面量,它保存的就是我们最终要找的类的全限定名“com/examples/test/GuoClass”。

通过上面的示例,我们可以分析出几个重点:

  • 常量池的容量计数是从1开始算起的。这和绝大多数语言习惯包括Java都不太一样。这样做的目的在于满足特定情况下,不引用常量池项目时,将索引值置为0。
  • 常量池的常量数量是不固定的。所以在常量池的前面需要放一个类型为u2的数据,代表常量池的容量。上面例子中constant_pool_count存放了十六进制数0x001B,即十进制27,代表常量池有(27-1 = 26)个常量,索引值范围是1~26。

  • 常量池中主要存放两大类常量:字面量(Literal)和符号引用(Symbolic References)。字面量类似于Java语言的文本或final常量值。符号引用包括:类和接口的全限定名、字段的名称和描述符以及方法的名称和描述符。
  • 常量池是最繁琐的数据,其中每一项常量都是一个表,除了第一位是一个u1类型的标志位(tag)以外,每一项常量均有各自的结构。如图是常量池的项目类型。

  • 常量池每一项数据类型依靠标志位(tag)来区分。知道了常量池数据项的类型之后,就可以根据常量项的结构总表来查询常量项的具体信息。

我们再来看一个例子,从总体上直观感受一下常量池的结构。还是解析上面的GuoClass.class类文件,图中红框1圈出来的是常量池的第一位的数据项,对应的是红框2圈出来的部分,它的数据类型是CONSTANT_Methodref_info,通过上图我们知道:

CONSTANT_Methodref_info的第一部分是一个u1类型的标志位(tag),红框1中的0x0A,即十进制10,正好对应标志位(tag)的枚举值CONSTANT_Methodref_info。

CONSTANT_Methodref_info的第二部分是一个u2类型的索引项,红框1中的0x0005,即十进制5,正好对应红框3中的cp_info#5。

CONSTANT_Methodref_info的第三部分是一个u2类型的索引项,红框1中的0x0017,即十进制23,正好对应红框3中的cp_info#23。

访问标志

如果你能看到这里,说明你已经跨过了学习Class文件格式最困难的部分,坚持下去一定会有收获!

紧接着常量池的是访问标志(access_flags),它用于识别类或接口层次的访问信息,比如这个Class是类还是接口;是否为public类型;有没有abstract修饰;有没有final关键字等等。具体标志位如图所示:

以GuoClass这个类为例,它是一个普通的Java类,不是接口、枚举或者注解,public类型,没有final和abstract关键字修饰,所以它的ACC_PUBLIC、ACC_SUPER标志应当为真,其他的标志位应该为假,所以它的access_flags的值应为:0x0001 | 0x0020=0x0021。从它的十六进制图可以看出,我们的结果是正确的。

类索引、父类索引与接口索引集合

类索引、父类索引和接口索引集合都按顺序排列在访问标志之后。

  • 类索引(this_class),u2类型数据,用于确定类的全限定名。
  • 父类索引(super_class),u2类型数据,用于确定类的父类的全限定名。由于Java语言不允许多重继承,因此父类索引只能有一个。除了java.lang.Object之外,所有的Java类都有父类,所以除了java.lang.Object之外,所有的Java类的父类索引都不为0。
  • 接口索引集合(interfaces),一组u2类型数据,用来描述这个类实现的接口。也就是这个类按implements语句后的接口顺序排列的集合。如果当前类是一个接口,则应当是extends语句后的接口。

类索引、父类索引和接口索引的查找过程是一样的,都是用u2类型的索引值表示,指向一个CONSTANT_Class_info类型的类描述符常量,再通过CONSTANT_Class_info类型的常量中的索引值找到CONSTANT_Utf8_info类型的全限定名字符串。

以GuoClass这个类为例,在access_flags之后的两个字节是this_class,这个u2类型的数据项用十六进制表示为0x0004,它指向常量池中第4个类型为CONSTANT_Class_info的常量,再根据此常量里的索引值找到常量池中第25个位置保存的CONSTANT_Utf8_info类型的字符串,这个字符串就是我们需要找的全限定名“com/examples/test/GuoClass”。

在this_class之后,即0x0004后面的四个字节,0x0005和0x0000分别表示super_class和interfaces集合的入口。super_class和this_class的查找过程一摸一样,而由于GuoClass没有实现的接口,所以它的入口值是0x0000,即常量池的0位置,这也是前文提到过的为什么常量池从1开始计数的原因。0在不引用常量值的时候使用。

字段表集合

前文已经介绍了Class文件里的常量池、访问标志和继承关系(类索引、父类索引、接口索引集合),那么在一个Java类的还剩下什么信息没有介绍呢?对!这一部分我们介绍类的字段。

字段(field)包括类变量和实例变量,但不包括方法内部声明的局部变量。字段数据项的类型是字段表(field_info),它用于描述接口或类中声明的变量。

字段表(field_info)结构

想象一下在Java里描述一个字段需要包含哪些信息?

  • 字段的作用域(public、private、protected)
  • 实例变量还是类变量(static)
  • 可变性(final)
  • 并发可见性(volatile)
  • 可否被序列化(transient)
  • 字段数据类型(基本类型、对象、数组)
  • 字段名称

字段表结构如图所示:

字段表的access_flags

字段表里的access_flags与类中的access_flags非常相似,它们都是u2类型,且都是访问标志。

除了字段数据类型和字段名称,其他的信息都是修饰符,都可以用布尔值来表示是否有某一个修饰符。字段访问标志位如图所示:

显然,ACC_PUBLIC、ACC_PRIVATE、ACC_PROTECTED只能三选一;ACC_FINAL、ACC_VOLATILE只能二选一;接口中的字段必须有ACC_PUBLIC、ACC_STATIC、ACC_FINAL标志。

以GuoClass为例,紧随上文中接口索引入口0x0000的是0x0001和0x0002。0x0001是字段表前面的fields_count数据项,fields_count用来对字段的数量计数,因为GuoClass只有一个int类型的字段money,所以fields_count作为一个u2类型的数据项,保存了两个字节,用十六进制表示数字1就是0x0001。紧随fields_count之后的就是字段表的access_flags数据项,它的值是0x0002,因为money字段是用private修饰的,所以对应的就是ACC_PRIVATE标志。

字段表的name_index和descriptor_index

紧接着access_flags标志的是两个索引值:name_index和descriptor_index。

name_index代表字段的简单名称,如果是在方法表里代表的是方法的简单名称。例如,make()方法的简单名称是“make”,money字段的简单名称是“money”。

descriptor_index代表字段或方法的描述符。描述符是用来描述字段的数据类型、方法的参数列表和返回值。描述符的标识字符如图所示:

当使用描述符描述数组类型时,使用一个前置的“[”,例如,一个整型数组可以被表示为“[I”,一个字符串类型的二位数组可以表示为“[[Ljava/lang/String;”。

当使用描述符描述方法时,按照先参数列表,再返回值的顺序,参数列表按照参数顺序放在一对小括号“()”里。例如,

  • 方法int make()的描述符为“()I”
  • 方法java.lang.String toString()的描述符为“()Ljava/lang/String;”
  • 方法int indexOf(char[]source,int sourceOffset,int sourceCount,char[]target,int targetOffset,int targetCount,int fromIndex)的描述符为“([CII[CIII)I”

以GuoClass为例,紧随access_flags的是name_index,如图十六进制0x0006;再来是descriptor_index,十六进制表示为0x0007。

我们可以把索引0x0006和0x0007在常量池中查找一下,0x0006保存的是一个CONSTANT_Utf8_info类型的字面量“money”,即字段的简单名称。

0x0007在常量池里保存为一个CONSTANT_Utf8_info类型的字面量“I”,即字段的描述符,表示money字段是int类型的。

至此,通过access_flags、name_index和descriptor_index查找到的信息,我们可以知道GuoClass里的字段信息是“private int money”。

descriptor_index之后还有一个属性表集合用于保存一些额外的信息。例如,"final static int money = 100000;" 会存在一项名称为ConstantValue的属性,其值指向常量100000。但是本例中的字段money没有额外信息,所以属性计算器为0。

方法表集合

方法表的内容基本上可以参照字段表的内容,因为它们的结构几乎一模一样都包括了访问标志(access_flags)、名称索引(name_index)、描述符索引(descriptor_index)、属性表集合(attributes)这几项。

方法表的access_flags

方法访问标志的取值如图所示。由于volatile和transient关键字不能修饰方法,所以在方法访问标志里去掉了ACC_VOLATILE和ACC_TRANSIENT。因为synchronized、abstract、native和strictfp关键字可以修饰方法,所以增加了ACC_SYNCHRONIZED、ACC_ABSTRACT、ACC_NATIVE和ACC_STRICTFP标志。

方法表里只不会保存有具体的代码信息,方法的java代码被编译器编译成字节码指令,保存在属性表集合的“Code”属性里。

public class GuoClass<T> {private int money;public int make() {return money + 1000000000;}
}

以GuoClass为例,方法表集合的第一个u2类型的数据是计数器容量,它的值为0x0002表示这个类文件有两个方法,其中一个显然就是代码中的make()方法,另外一个比较隐蔽,是实例的构造器方法,构造器方法是编译器自动添加的方法。构造器方法是public公有的,所以访问标志是ACC_PUBLIC,对应的十六进制数是0x0001。

方法表的name_index和descriptor_index

紧挨着构造器方法的访问标志位的是u2类型的方法名称索引,其值为0x0008,在常量池中我们可以查询到方法名称为""。

再往后是u2类型的方法描述符索引,其值为0x0009,在常量池中我们可以查询到方法描述为"()V"。前面我们已经提到过这个描述符的含义表示方法没有参数,并且返回空void。

属性表计数器的值为0x0001,表示属性表集合有一个属性。属性名称索引为0x000A,在常量池中查询到其值为“Code”,说明此属性是方法的字节码描述。

方法重载

在Java语言中,要重载一个方法,除了方法名要相同外,方法参数的个数或类型不能一样。但是在Class文件格式中,只要描述符不是完全一致的两个方法也可以共存,即如果两个方法具有相同的方法名,方法参数的个数和类型也一样,但返回值类型不同,这两个方法也是可以合法共存于同一个Class文件里的。

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

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

相关文章

【JVM精讲与GC调优教程(概述)】

如何理解虚拟机(JVM)跨语言的平台 java虚拟机根本不关心运行在其内部的程序到底是使用何种编程语言编写的,他只关心“字节码”文件。 java不是最强大的语言,但是JVN是最强大的虚拟机。 不存在内存溢出? 内存泄露? JAVA = (C++)–; 垃圾回收机制为我们打理了很多繁琐的…

【人工智能入门学习资料福利】

总目录如下&#xff08;部分截取&#xff09;&#xff1a; 百度网盘链接&#xff1a;https://pan.baidu.com/s/1bfDVG-xcPR3f3nfBJXxqQQ?pwdifu6 提取码&#xff1a; ifu6

B站已经部分上线前台实名,如不同意实名,后期账号流量将收影响!

B站部分百万粉丝博主的主页显示账号运营人名字的政策是从10月31日开始的。当天&#xff0c;B站官方发布了《哔哩哔哩关于头部“自媒体”账号前台实名的公告》&#xff0c;表明了其前台实名制的实施计划。 B站部分上线前台实名的过程可以追溯到2021年。当时&#xff0c;中国政府…

SQL语句执行过程

一条 SQL 的执行过程可以大致分为以下几个步骤&#xff1a; 连接器&#xff1a; ○ 客户端与数据库建立连接&#xff0c;并发送 SQL 语句给数据库服务。 ○ 连接器验证客户端的身份和权限&#xff0c;确保用户有足够的权限执行该 SQL 语句。查询缓存&#xff1a; ○ 连接器首先…

基于鹰栖息算法优化概率神经网络PNN的分类预测 - 附代码

基于鹰栖息算法优化概率神经网络PNN的分类预测 - 附代码 文章目录 基于鹰栖息算法优化概率神经网络PNN的分类预测 - 附代码1.PNN网络概述2.变压器故障诊街系统相关背景2.1 模型建立 3.基于鹰栖息优化的PNN网络5.测试结果6.参考文献7.Matlab代码 摘要&#xff1a;针对PNN神经网络…

Motion v5.6.7 苹果电脑上的视频编辑

Motion mac是一款运行在苹果电脑上的视频编辑软件&#xff0c;它能让您自定Final Cut Pro字幕、转场和效果。 它可以在2D或3D空间中创建您自己的精美炫目的动画&#xff0c;同时还能在您工作时提供实时反馈。广色域支持让你的动态图形更显出色光彩。3D文字功能经过优化增强&am…

01背包与完全背包学习总结

背包问题分类见下图 参考学习点击&#xff1a;代码随想录01背包讲解 01背包问题&#xff1a; 核心思路&#xff1a; 1、先遍历物品个数&#xff0c;再遍历背包容量。因为容量最先是最大的&#xff0c;往背包里放物品&#xff0c;所以背包容量在慢慢减少&#xff0c;但背包容量…

上海泗博MODBUS转PROFINET网关TS-180 网关连接LED显示屏应用案例

项目 常州某钢铁公司的轧钢车间为了更清晰地显示当天轧钢系统各环节的工作参数&#xff0c;如轧钢的日期、钢种、吐丝机设备运行情况等&#xff0c;引进了另一家为其定制的LED显示屏。轧钢系统各环节的设备参数通过西门子S7-1500PLC采集后&#xff0c;实时显示在LED显示屏上&am…

飞瓜数据B站丨B站UP主11月第3周榜单排行榜榜单(B站平台)发布!

飞瓜轻数发布2023年11月13日-11月19日飞瓜数据UP主排行榜&#xff08;B站平台&#xff09;&#xff0c;通过充电数、涨粉数、成长指数、带货数据等维度来体现UP主账号成长的情况&#xff0c;为用户提供B站号综合价值的数据参考&#xff0c;根据UP主成长情况用户能够快速找到运营…

Linux网络——传输层

目录 一.再谈端口概念 二.UDP协议 1.UDP协议格式 2.UDP的特点 3.面向数据报 4.UDP的缓冲区 5.UDP使用注意事项 6.UDP协议在内核中的表现形式 7.基于UDP的应用层协议 三.TCP协议 1.TCP协议格式 2.TCP确认应答机制 3.超时重传机制 4.TCP报文六位标志位 5.滑动窗口 6…

Flutter开发实践:用一套代码构建多端精美应用

&#x1f3c6;作者简介&#xff0c;黑夜开发者&#xff0c;CSDN领军人物&#xff0c;全栈领域优质创作者✌&#xff0c;CSDN博客专家&#xff0c;阿里云社区专家博主&#xff0c;2023年6月CSDN上海赛道top4。 &#x1f3c6;数年电商行业从业经验&#xff0c;历任核心研发工程师…

Python下使用requests库遇到的问题及解决方案

每一盏灯都有一个故事……当凌晨2点我的房间灯还亮着时&#xff0c;那就是我与BUG的一场生死博弈。一个人静静地坐在电脑前不断地写代码&#xff0c;感觉快要麻木了&#xff0c;好比闭关修炼一样枯燥无味。最终当我打通任督二脉后&#xff0c;bug修复迎来的一片曙光。 一、问题…

clang+llvm多进程gdb调试

clangllvm多进程gdb调试 前言1. 命令行gdb2. 父进程调试3. 子进程调试4. 返回父进程 前言 在学习新增llvm的优化pass时&#xff0c;需要跟踪clang及llvm的调用栈。然而llvm通过posix_spawn()创建了新进程&#xff0c;这使得gdb调试必须有一定的技巧了。 1. 命令行gdb 以下命…

函数式编程-Stream流笔记-三更草堂

函数式编程-Stream流 1. 概述 1.1 为什么学&#xff1f; 能够看懂公司里的代码 大数量下处理集合效率高 代码可读性高 消灭嵌套地狱 //查询未成年作家的评分在70以上的书籍 由于数据中作家和书籍可能出现重复&#xff0c;需要进行去重 List<Book> bookList new Ar…

4G5G智能执法记录仪在保险公司车辆保险远程定损中的应用

4G智能执法记录仪&#xff1a;汽车保险定损的**利器 随着科技的不断进步&#xff0c;越来越多的智能设备应用到日常生活中。而在车辆保险定损领域&#xff0c;4G智能执法记录仪的出现无疑是一大**。它不仅可以实现远程定损&#xff0c;还能实现可视化操作、打印保单以及数据融…

给定一个非严格递增排列的有序数组,删除数组中的重复项

实例要求&#xff1a;1、给定一个非严格递增排列的有序数组 nums &#xff1b;2、原地 删除重复出现的元素&#xff0c;使每个元素 只出现一次 &#xff1b;3、返回删除后数组的新长度&#xff1b;4、元素的 相对顺序 应该保持 一致 &#xff1b;5、然后返回 nums 中唯一元素的…

dolphinscheduler有任务一直在运行(问题)目前对数据库解决

dolphinscheduler有任务一直在运行&#xff08;问题&#xff09;目前对数据库解决 危害&#xff1a; 这么多的任务没有结束&#xff0c;会涉及很多问题的&#xff0c;系统的数据盘会不断入职日志&#xff0c;数据量很大&#xff0c; 其实对于dolphinscheduler的性能是下降的&a…

WMware虚拟机与主机互相共享文件安装VMware Tools灰色无法点击安装解决方案

一、背景 虚拟机与主机互传文件最简单的方法&#xff0c;就是给虚拟机系统安装VMware Tools。 安装VMware Tools后虚拟机系统和主机的文件可以相互拖拽&#xff0c;文字也可以任意粘贴复制。 二、遇到的问题 使用VMware时&#xff0c;安装VMware Tools或者重新安装VMware To…

假期对企业邮箱的维护和管理策略

假期应该对企业邮箱做些什么&#xff1f;放假后对企业邮箱的自动回复设置将在这里单独列出。自动回复是你与新老客户沟通的桥梁。告诉老客户你放假了&#xff0c;但你会花时间回复他。还告诉新客户&#xff08;新询价客户&#xff09;你在假期不能及时回复他&#xff0c;他们会…

m4s格式视频文件如何转mp4?三个方法教会你!

m4s格式是一种视频分片格式&#xff0c;它将视频文件分成多个小块&#xff0c;方便网络传输和播放。这种格式常用于流媒体服务&#xff0c;如在线视频网站、直播平台等&#xff0c;比如B站哔哩哔哩下载下来的视频就是这种格式。 方法一&#xff1a;野葱视频转换器 一款音视频转…