字节码文件结构

news2024/12/23 11:37:56

目录

1、概述

2、JVM的两个无关性

3、Class字节码文件的结构

1、基本存储单位

2、字节码文件数据结构

3、Class文件格式

4、魔数与Class文件的版本

5、常量池

6、访问标志

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

8、字段表集合

9、方法表集合

10、属性表集合

11、总结

4、字节码指令

1、概述

2、字节码指令集的特点

3、字节码与数据类型

4、指令集包含哪些指令


1、概述

计算机只能执行机器码(机器指令,二进制的0和1),所以写的代码需要被编译器编译成机器码才能被计算机执行。

但虚拟机的蓬勃发展提供了第二种选择,即可以选择把代码编译成“与操作系统和机器指令集无关的,针对虚拟机平台的格式”,再交由虚拟机去执行。

2、JVM的两个无关性

有很多种硬件指令集,也有很多种操作系统。要实现一个程序可以在任何操作系统和硬件组成的平台上运行,就必须在操作系统之上实现。

即设计一种所有平台都支持的程序存储格式:字节码,再为每个平台设计一个规格相同的虚拟机,就可以实现屏蔽差异。

实际上,JVM有两种无关性:

  • 平台无关性
  • 语言无关性:任何语言编译成的class字节码文件,只要符合JVM规范,都可以在JVM上运行。

实现无关性,虚拟机、字节码格式,二者缺一不可

3、Class字节码文件的结构

1、基本存储单位

Class文件是一组以8个字节为基础单位的二进制流,各个数据项目顺序、紧密地排列,没有任何分隔符,使得整个Class文件的内容全部都是程序运行的必要数据。

如果遇到需要占用8个字节以上空间的数据项,按照高位在前的方式,将它分割成若干个 8个字节 进行存储。

2、字节码文件数据结构

无符号数和表

Class文件格式的数据结构,只有两种数据类型:无符号数、表

  • 无符号数:
    • 基本数据类型
    • u1、u2、u4、u8表示1个字节、2个字节、4个字节、8个字节的无符号数(注意1个字节 = 2个十六进制数)
    • 可以用来表示数字、索引引用、数量值或按照UTF-8构成字符串值
  • 表:
    • 由多个无符号数或其他表构成的复合数据类型
    • 常用_info结尾
    • 用于描述有层次关系的复合结构的数据
    • 整个Class文件本质上也可以看作一张表

集合

无论是无符号数还是表,当需要描述同一类型但数量不确定的多个数据,需要使用一个前置的容量计数器 + 若干个连续数据项的形式,这种形式的数据称为“集合”。

3、Class文件格式

Class文件格式如下图。这张表的结构,不论是顺序、数量、字节长度,都是被严格限定的,全部不允许改变

4、魔数与Class文件的版本

魔数

每个Class文件的开头4个字节,被称为“魔数(Magic Number)”。

魔数的唯一作用是,确定这个文件是否是一个可以被虚拟机接受的Class文件

Class文件的魔数值为“0xCAFEBABE”。

为什么需要魔数

不只是Class文件,很多文件格式标准都使用魔数来进行身份识别,因为它比文件扩展名更可靠。

文件格式的制定者可以随便选一个内容作为魔数的值,只要没有和其他格式撞车。

Class文件的版本号

魔数后面的4个字节,存储的是这个Class文件的版本号:

  • 第5、6个字节是次版本号
  • 第7、8个字节是主版本号

这个版本号指的是该Class文件对应JDK的版本号,JVM拒绝执行超过其要求版本号的Class文件,但能向下兼容。

例如JDK 1.1 能支持版本号的范围是45.0 ~ 45.65535,而JDK 13可生成的主版本号最大为57.0

主版本号与次版本号

主版本号代表JDK的大版本号,每个版本+1。次版本号在早期被使用,从JDK 1.2后,次版本号全部固定为0。在JDK 12之后,JDK的功能太多,一些新特性需要以“公测”的形式放出,所以副版本号重新被启用。如果使用了这种“技术预览版”的JDK,生成的字节码文件会把次版本号标识为65535,便于JVM分辨。

示例

比如随便打开一个Class文件(以十六进制查看)

字节码文件:

 

可以看到,前四个字节是魔数cafebabe,第5、6个字节是次版本号:0x0000,第7、8个字节是主版本号:0x0034,十进制的52,这是JDK8的版本号。 

 

5、常量池

1、常量池的容量

在版本号后面的是常量池,它是占用Class文件空间最大的数据项之一,属于表类型。

常量池中的常量数目是不确定的,所以在入口设置了一个u2类型的数据,代表常量池的容量(constant_pool_count),它是从1开始的。这样设计可以把0空出来,作为“表示不引用任何一个常量池项目的含义”之用。

比如上图的字节码文件,第9、10个字节为0x0018,十进制为24,代表常量池中有23个常量,索引范围是1~23

2、常量池的内容

常量池中存放两大类常量:字面量、符号引用。

  • 字面量:类似Java中的常量概念,比如字符串、声明为final的属性值
  • 符号引用:包括以下几类常量
    • 被模块导出或者开放的包
    • 类和接口的全限定名
    • 字段的名称和描述符
    • 方法的名称和描述符
    • 方法句柄和方法类型
    • 动态调用点和动态常量

Java代码在编译成Class文件之后,Class文件中不会保存各个方法、字段最终在内存中的布局信息,即无法得到真实的内存地址,无法直接被虚拟机使用。

当虚拟机进行类加载时,会从常量池获得对应的符号引用,再在类创建或运行时解析、翻译到具体的内存地址中。

3、常量

常量池中的每一个常量都是一个表,起始的第一位是一个u1的标志位(表示常量的类型)

每种类型的常量都有着自己的结构,各不相同,内容包括标志位(tag)、长度(length)、有效值(bytes)等。

 

比如这个字节码文件,常量池的第一个常量,标志位是0x0a,十进制为10,对应的常量类型是CONSTANT_Methodref_info 

4、javap

在JDK的bin目录中,Oracle提供了一个分析字节码文件的工具:javap,添加-v参数,可以输出字节码内容。

来看常量池部分:

可以看到,第一个常量确实是Methodref类型,和我手工分析的一样。

此外,还出现了很多代码中没有的常量,比如I、V、、LineNumberTable等。这些是编译器自动生成的,会被其他内容所引用。

它们用来描述一些不便于使用固定字节表达的内容,比如方法的返回值、参数个数以及参数类型等。因为Java中类的个数是无穷尽的,不能使用无符号数来表示每个类,只能通过常量表中的符号引用进行表示。

6、访问标志

常量池结束后,紧挨着的两个字节代表访问标志(access_flags),用于识别一些类或者接口层次的访问信息,包括:

  • 这个Class是类还是接口
  • 是否为public修饰
  • 是否为abstract修饰
  • 等等

access_flags占两个字节,有16个标志位可以使用,但目前只定义了其中9个,没有使用到的一律为0

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

访问标志之后是类索引(this_class)、父类索引(super_class),接口索引集合(interfaces)。

  • 类索引:u2类型,用于确定这个类的全限定名
  • 父类索引:u2类型,只有一个(Java是单继承的,除了java.lang.Object之外,所有Java类的父类索引都不为0)
  • 接口索引集合:是一组u2类型数据的集合。这个类实现的接口,按代码书写的顺序从左到右排列在这个集合中。

这三项数据可以确定一个类的继承关系。

类索引和父类索引各自指向一个类型为CONSTANT_Class_info的类描述符常量。

通过这个常量中的索引值,可以找到定义在CONSTANT_Utf-8_info类型的常量中的全限定名字符串。

接口索引集合,入口处为一个u2类型的接口技术企,表示表的容量。(如果没有实现任何接口,计数器为0,表不占用任何字节)

8、字段表集合

字段表(field_info)用于描述类或接口中声明的变量。

Java中的字段(Field)是指成员变量,包括静态属性(类级)和非静态属性(对象级),但不包括方法内部声明的局部变量。

一个字段可以包括这些信息:

  • 访问权限修饰符(public、default、private、protected)
  • 是否为static静态的
  • 是否为final
  • 并发可见性(是否为volatile,强制从内存读写)
  • 可否被序列化(transient)
  • 字段的数据类型(基本类型、引用类型)
  • 字段名称

要描述整个字段:

  • 有些适合使用标志位(比如修饰符,总数是有限的,可以被枚举)
  • 而有些信息,比如数据类型和字段名,无法被枚举,只能应用常量池的常量来描述

最终,字段表设计成了这样:

  • access_flags表示字段的修饰符,具体含义如下:

描述符的扩展

对于数组类型,每一维度会使用一个前置的“[”。

比如“

  • 一个String[][]二维数组,被记录成 [[Ljava/lang/String; (全限定名后面会跟一个分号,表示结束)
  • 一个int[]一维数组,被记录成 [I

描述符描述方法时,按照“先参数列表,后返回值”的顺序来描述。(因为修饰符已经用标志位表示过了,方法名也引用过,所以只剩下返回类型和参数列表需要表示)

参数列表按照参数的从左到右顺序,放在一组小括号内。

比如这个方法:

描述符:()Ljava/lang/String;

一个Class文件的字段表中不会列出从父类或父接口继承而来的字段,但可能会出现Java代码中不存在的字段,这是因为编译器做了处理。

比如内部类中为了保持对外部类的访问性,内部类编译后,编译器就会自动添加指向外部类实例的字段。

通过描述符,还可以进行字段的合法性检验。只要重名的两个字段,描述符不完全一样,那就是合法的。

9、方法表集合

Class文件中对方法的描述,和字段的处理方法类似。

方法表也包括这几部分:访问标志、名称索引、描述符索引、属性表集合。

方法体中的代码,存放在方法表中的属性表中名为“Code”的属性中。

一个类的Class文件中,不会包含从父类继承来的方法(只要没有重写或重载),但会出现编译器自动添加的方法,最常见的有两个:

  • ():类构造器方法
  • ():实例构造器方法

它们是用于进行“前端编译与优化”的

为什么重载不能以返回值不同为依据

Java中,要重载一个方法,除了两个方法的简单名称要相同之外,还要求必须有一个和原先方法不同的“特征签名”(指Java代码中的)。

特征签名有两种:

  • Java代码中的特征签名包括:方法名称、参数顺序和参数类型。返回值不包含在这个签名中,所以无法依靠返回值对一个方法进行重载。
  • 字节码的特征签名则范围更广,除了包括方法名称、参数顺序和参数类型之外,还包含返回值以及受查异常表。

另一个方面,只要描述符不完全一致的两个方法(比如有相同的名称和特征签名,但返回值不同),是可以合法存在于同一个Class文件中的。

(这种情况无法通过Java的编译,但JVM是可以支持的,语言无关性的体现!)

10、属性表集合

Class文件、字段表、方法表都可以携带自己的属性表集合,来描述一些专用信息。

属性表集合的限制相较于Class文件中的其他数据项目,稍微宽松一些,不要求各个属性表的严格顺序。只要不与官方的属性名重复,自己实现的编译器可以向属性表中插入任何属性,JVM遇到自己不认识的属性会忽略掉。

《Java虚拟机规范》定义了一大堆官方属性。对于每一个属性,它的名称都要从常量池中应用一个CONSTANT_Utf-8_info类型的常量来表示,而属性值是自定义的,只需要通过一个u4长度的属性去说明属性值占用的位数即可。

因此,从JDK最早版本到现在,Class文件的结构几乎没有发生过变化,新特性只需要在属性表中添加新属性就可以实现支持。

11、总结

Class字节码文件是一个二进制文件,可以用16进制打开查看细节。使用javap- v可以更加直观,把每个部分都分割好了

里面包含这个类的全部信息。

4、字节码指令

1、概述

Java虚拟机的指令由1个字节长的操作码,以及跟随其后的零至多个操作数(代表此操作的参数)组成。

由于JVM采用的是面向操作数栈的架构,所以大多数指令都不包含操作数,只有一个操作码。指令参数存放在操作数栈中。

2、字节码指令集的特点

字节码指令集的优缺点都很明显。

缺点:

  • 由于限制了JVM操作码的长度为1个字节(8位,0~255),所以指令的总数不能超过256条。
  • Class文件格式放弃了编译后代码的操作数长度对齐,虚拟机在操作长度超过1字节的数据时,只能在运行时从字节中重建出它的具体数据结构,损失一些性能。

优点:

  • Class文件可以省略掉大量的分隔符,获得数据量小的优势,便于在网络上高效率传输。这也是Java的初衷。

3、字节码与数据类型

JVM的指令集,大多数指令都对应着具体的数据类型,不是通用的。

比如:

  • iload指令,从局部变量表中加载int类型的数据到操作数栈中
  • 同样的操作,加载float类型需要使用fload指令。

但由于指令个数有限,无法为每种数据类型都唯一安排一个专属的操作指令,所以有一些单独的指令可以在必要的时候,将一些不被支持的类型转换为可被支持的类型。比如byte、short、boolean和char。

大多数指令都没有照顾到这四种类型,所以编译器会在编译期或运行期:

  • 将byte和short类型“带符号扩展”成int类型
  • 将boolean和char“零位扩展”成int类型

使用int类型的指令来处理。所以大多数对于byte、short、boolean和char类型的操作,实际上都是扩展成int类型来进行的。

JVM对int类型的支持非常完善,很多操作都是最终转化成int类型来进行的。

4、指令集包含哪些指令

一笔带过,作为了解

  • 加载和存储指令:用于将数据在栈帧中的局部变量表和操作数栈之间来回传输
  • 运算指令:对操作数栈上的两个值进行运算(加减乘除、按位运算、自增、比较)
  • 类型转换指令:实现显式类型转换(JVM自动支持低向高类型转换,即隐式类型转换),或用来处理数据类型与指令类型无法对应的问题
  • 对象创建与访问指令:创建类实例、访问类字段、检查实例类型
  • 操作数栈管理指令:直接控制 操作数栈
  • 控制转移指令:修改PC寄存器的值,使得可以从指定位置指令的下一条指令继续执行程序。
  • 方法调用和返回指令
  • 异常处理指令:处理throw操作,以及自动抛出一些运行时异常。catch语句不是由字节码指令完成的,而是采用异常表完成的。
  • 同步指令

 

 

 

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

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

相关文章

centos7.9升级rockylinux8.8

前言 查看centos的版本 ,我这台服务器是虚拟机,下面都是模拟实验 升级前一定要把服务器上配置文件,数据等进行备份 [rootlocalhost ~]#cat /etc/redhat-release CentOS Linux release 7.9.2009 (Core) [rootlocalhost ~]#uname -a Linux jenkins_ser…

Ubuntu常见基本问题

系列文章目录 文章目录 系列文章目录一、复制粘贴问题二、无法全屏问题三、设置为中文四、时间同步问题1、选择时区2、同步网络时间 一、复制粘贴问题 开启终端:ctrlaltt卸载已有工具 sudo apt-get autoremove open-vm-tools安装工具open-vm-tools sudo apt-get …

echarts的y轴数据显示过长占不下,内容截取,鼠标hover上去显示全部

初始效果: 优化后的效果: 优化点:控制了y轴显示字数,鼠标hover上去显示全部 主要实现思路参考了这位同学的文章:https://www.cnblogs.com/liuboren/p/9040622.html 我是用vue实现的,因为我需要一个页面中…

各算法/协议知识理论笔记(fpga)

一、利用fifo对3行数据求和 需要2个fifo保存第0行和第1行的数据,如下图 比如有20行数据,则将一行一行的输给fifo2, fifo2出来的数据再给fifo1.当fifo和fifo1有数据时,在准备给 fifo2输入新的一行数据时,同时读出fifo2,…

Linux进程间通信(信号)

信号发送 信号是 Linux 系统响应某些条件而产生的一个事件,接收到该信号的进程会执行相应的操作。 信号的产生有三种方式: (1)由硬件产生,如从键盘输入 CtrlC 可以终止当前进程 (2)由其他进程发送,如可在 …

PostgreSQL修炼之道之高可用性方案设计(十七)

目录 20 高可用性方案设计(二) 20.2 基于共享存储的高可用方案 20.2.1 SAN存储的方案 20.2.2 DRBD的方案 20.3 WAL日志同步或流复制同步的方案 20.3.1 持续复制归档的standby的方法 20.3.2 异步流复制的方案 20.3.3 基于同步流复制方案 20.4 基于…

国内外低代码开发平台发展情况如何?

国内外低代码开发平台发展情况如何?之前有些过很多关于低代码的内容,这篇就来详细梳理下国内外低代码开发平台发展现状。 关于低代码解读看这篇>>什么是低代码(Low-Code)?关于低代码平台看这篇>>主流的开…

业务高速增长,如祺出行如何用腾讯云消息队列 RocketMQ 应对挑战

导语 作为广汽集团旗下的智慧出行平台,如祺出行上线四年时间,用户规模和订单量保持高速增长。在过去的2022年,如祺出行平台累计注册用户突破1800万,同比增长64%,年度订单总量超7000万,同比增长52%。 高速…

【MCS-51】串行I/O接口及其通信

我们知道MCS-51中有很多的引脚,这些引脚很多一般都是用作输入或者输出口,其中有两个引脚P3.0和P3.1比较特殊,我们常将其用作串行通信的数据发送和接收端TXD、RXD。 目录 🐱通信方式 🐱串行通信的传输方式和数据通信…

python笔记17_实例演练_二手车折旧分析p2

…… 书接上文 4.车辆等级维度 探查车龄为5年的车辆,折旧价值与车辆等级的关系。 # 筛选出车龄为5的数据创建新表 data_age5 data[data[age] 5] data_age5 # 分组聚合计算均值 data_car_level data_age5.groupby(car_level_name)[lowest_price].mean().reset…

16.2:岛屿数量问题

文章目录 岛屿数量问题方法一:采用递归的方法方法二:使用并查集的方法(map)方法三:使用并查集的方法(数组) 岛屿数量问题 测试链接:https://leetcode.com/problems/number-of-islan…

大数据:分布式计算,MapReduce,hadoop的计算组件,hive是sql分布式计算框架,底层就是基于MapReduce的

大数据:分布式计算,MapReduce,hadoop的计算组件,hive是sql分布式计算框架,底层就是基于MapReduce的 2022找工作是学历、能力和运气的超强结合体,遇到寒冬,大厂不招人,可能很多算法学…

【C++】一文带你入门 STL

一 STL 组成 graph LRA[STL] --- B[容器 container]A --- C[配接器 adapter]A --- D[迭代器 iterator]A --- E[仿函数 function]A --- F[算法 algorithm]A --- G[空间配置器 allocator]二 常用容器 容器简介 下面我们来简单看一下这些容器的常用接口的使用,并分析…

更新中-深度学习实战中遇到的一些概念+少量代码

onnx ONNX 是一种用于机器学习模型的开放式表示格式,它可以让不同的深度学习框架之间共享模型。 import onnxruntime # 加载模型 session onnxruntime.InferenceSession(model.onnx) # 运行模型。第一个参数是输出变量列表,不指定的话返回所有值 outp…

ESP8266使用MicroPython接入ThingsBoard

1、概述 我们老大当初叫我学习microPython,这个可以直接将代码发到板子上,然后就可以跑,就相当于设备业务代码由我们来写,不仅仅是让嵌入式来写,嵌入式做的就是封装函数,我们可以调用.最终这个还是实现了,但是没有推广. 2、设备 我自己购买的设备是ESP8266,某宝上购买的,mic…

智能仓储系统哪家公司做的比较好?求推荐排名不错的智能仓储公司?

什么是仓储服务信息平台?仓储服务信息平台可以为企业提供哪些便利? 随着电商和物流行业的快速发展,仓储服务越来越受到人们的关注。为了更好地管理仓储服务,提高效率,降低成本,仓储服务信息平台也应运而生…

CTF国赛2023 - ukfc(四道逆向已下班)

没啥好说的,惜败已复现:badkey1、国粹、ezbyte、moveAside、ezAndroid Notice:复现时候的一些题解来源于各大战队的wp,比如F61d,侵删 Re ezbyte 首先跟踪很容易分析到前后缀 至于里面的,得知道是dwarf…

哪个牌子的电容笔好用?Apple Pencil平替

随着时代的进步,数码产品在人们日常生活中的使用频率越来越高,一个iPad和一支电容笔似乎已然成为人们主要的学习工具了。电容笔的发展速度很快,在众多的电容笔牌子中,什么牌子好用又便宜?下面,我来给大家推…

深度学习进阶篇[8]:对抗神经网络GAN基本概念简介、纳什均衡、生成器判别器、解码编码器详解以及GAN应用场景

【深度学习入门到进阶】必看系列,含激活函数、优化策略、损失函数、模型调优、归一化算法、卷积模型、序列模型、预训练模型、对抗神经网络等 专栏详细介绍:【深度学习入门到进阶】必看系列,含激活函数、优化策略、损失函数、模型调优、归一化…

FastReport Business Graphics .NET 2023.1 Crack

FastReport 商业图形 .NET .NET WinForms 的数据可视化库 使用 FastReport 业务图形库,您可以可视化不同层次的数据,构建业务图表以供进一步分析和决策。 所有这些都可以直接在您的应用程序中工作! 利用 .NET 7、.NET Core、Blazor、ASP.NE…