当前位置: 首页 > news >正文

Java字节码文件解析

1.怎么查看字节码文件

  1. javap -v class文件名

在这里插入图片描述

  1. 通过jclasslib查看,idea里也有jclasslib插件
    在这里插入图片描述

上面两种方式查看的其实都不是最原始的二进制字节码,是经过翻译整理过的。

  1. 将class文件以十六进制格式查看,notepad++就可以
    在这里插入图片描述

对十六进制打开的字节码文件的疑惑:

1.为什么是两个十六进制数一对一对的书写的?

在十六进制表示中,每个字节用两个十六进制数字表示。例如,ca fe ba be代表四个字节的数据。

为什么能用两个16进制数字表示一个字节?
一个字节(Byte)由 8 个二进制位(bit)组成,每个二进制位可以是 0 或 1。因此,一个字节可以表示的范围是:
二进制:00000000 到 11111111
十进制:0 到 255
十六进制:00 到 FF

所以,ca fe ba be等都是一个个字节的数据。

2.知道了上面后,可以看出一行是16个字节的数据,最左边一列的Address是内存地址,第一行首地址是00000000,第二行的首地址怎么跳到了00000010了?

00000010是十六进制数字,对应的十进制是16!用10进制来描述前两行的内存地址,就是第一行是0~15,第二行的起始地址就是16。

题外话:为什么内存地址基本上都用16进制来表示?

这里贴一下gpt的回答:

在这里插入图片描述

明白了上面两个疑惑,基本上就能阅读十六进制打开的字节码文件了。例如前四个字节是class文件的魔术:ca fe ba be,紧接着是副版本号和主版本号,例如上面的主版本号是003d,转换成十进制为61,主版本号61对应着JDK17的版本。

2.class文件里的数据类型

  1. 无符号数: 以u1、u2、u4、u8来分别代表1个字节、2个字节、4个字节和8个字节的无符号数
  2. 表:表是由多个无符号数或其他表构成的复合数据结构。所有的表都以“_info”结尾。 由于表没有固定长度,所以通常会在其前面加上个数说明。

在字节码中,所有的数据要么是一个无符号数字,要么是个表。

3.class文件结构

字节码中按照顺序依次存储的下面表格中的内容:

类型名称说明长度数量
u4magic魔数,识别Class文件格式4个字节1
u2minor_version副版本号(小版本)2个字节1
u2major_version主版本号(大版本)2个字节1
u2constant_pool_count常量池计数器2个字节1
cp_infoconstant_pool常量池表n个字节constant_pool_count-1(为什么是减1后面会说)
u2access_flags访问标识2个字节1
u2this_class类索引2个字节1
u2super_class父类索引2个字节1
u2interfaces_count接口计数器2个字节1
u2interfaces接口索引集合2个字节interfaces_count
u2fields_count字段计数器2个字节1
field_infofields字段表n个字节fields_count
u2methods_count方法计数器2个字节1
method_infomethods方法表n个字节methods_count
u2attributes_count属性计数器2个字节1
attribute_infoattributes属性表n个字节attributes_count

4.class文件结构中的17项

class文件由哪些部分组成?

魔数、副主版本、常量池、访问标识、类索引、父类索引等

4.1魔术

class文件格式的特殊标志,事实上,每个文件格式都有自己的魔术,你以16进制格式打开几张png图片,你会发现,前一段内容都是一样的,这就可以看成是png文件的魔术。

4.2副主版本

编译器的版本,可以查出JDK的版本。

4.3常量池

常量池是class文件中最重要的部分。

要强调的是,该处是字节码文件中的常量池,本质上还是磁盘上的.class文件代码,和运行时的内存还没关系。字节码中的常量池部分会在jvm运行时加载到方法区的运行时常量池中,运行时常量池是方法区的一部分。并且从jdk1.7开始,字符串常量从方法区的运行时常量池中挪到了堆中,叫字符串常量池了。

常量池中存储着编译时期生成的各种字面量和符号引用,所以常量池的主要作用是class文件中字段和方法的解析,在解析字段和方法时,需要知道字段名、方法名、方法的形参、方法的返回类型等,这些信息都存储在常量池当中。

字节码中没有分隔符,所以需要指定常量池中有多少项也就是有多少个常量,所以字节码中紧跟着主版本的是常量池计数器,来指定常量池中有多少项,占两个字节。需要说明的是,如果常量池计数器大小为22,那么常量池中的常量个数为22-1=21,常量索引依次为1~21(从1开始,而不是0)

通常代码中的计数都是从0开始例如数组,这里为什么是从1开始的?因为它把第0项空出来了,这是为了满足后面某些指向常量池的索引值的数据在特定情况下需要表达“不引用任何一个常量池项目”的含义。

常量池中主要存储着两大类:字面量(Literal)和符号引用(Symbolic Reference),包含了class文件结构及其子结构中引用的所有字符串常量、类或接口名、字段名和其它常量(比如final修饰的常量)。

对字面量和符号引用的解释

字面量:文本字符串和声明为final的常量

例如:

public class Test {final int A = 11;String s = "aaaaaaaaaaaaaaaaaa";public static void main(String[] args) {}
}
//"aaaaaaaaaaaaaaaaaa"就是文本字符串,A就是一个常量值为11
//在编译后的字节码中的常量池中能直接找到aaaaaaaaaaaaaaaaaa和11

符号引用:包括类和接口的全限定名、字段的名称和描述符、方法的名称和描述符。

全限定名:例如org/example/demo3/demos/demo/Example就是全限定名,就是把包名(org.example.demo3.demos.demo.Example)中的.换成了/

描述符:描述符是用来描述字段的数据类型、方法的参数列表、方法的返回类型。

描述符的规则:

标志符含义
B基本数据类型byte
C基本数据类型char
D基本数据类型double
F基本数据类型float
I基本数据类型int
J基本数据类型long
S基本数据类型short
Z基本数据类型boolean
V代表void类型
L对象类型,比如:Ljava/lang/Object;
[数组类型,代表一维数组。比如:double[][][] is [[[D

例如:
在这里插入图片描述

这一项存储着a字段的符号引用,它的名称是a,描述符是I,也就是基本数据类型int

上图的额外解释:上图的名字和描述符其实指的分别是一个索引,分别是cp_info#11和cp_info#12。(cp_info表示常量池,#11表示常量池中索引为11的位置)。后面的<a>和<l>是jclasslib工具帮我们显示出来的。点到11索引和12索引看看:

在这里插入图片描述

在这里插入图片描述

用描述符的标志符也可以解释我们平时打印数组时的情况

 public static void main(String[] args) {Object[] objects = new Object[10];System.out.println(objects);}

输出:
在这里插入图片描述
打印出来的[L是什么东西?

[表示的是数组类型,L表示的是对象类型,java.lang.Object是包名,分号是内存地址。

常量池中的每一项都是下面表格中的一种类型,都有相同的特征,第一个字节是类型标志,是一个十进制数字,用于确定该项的类型,这个字节称为tag byte(标记字节、标签字节)。

类型标志(或标识)描述
CONSTANT_utf8_info1UTF-8编码的字符串
CONSTANT_Integer_info3整型字面量
CONSTANT_Float_info4浮点型字面量
CONSTANT_Long_info5长整型字面量
CONSTANT_Double_info6双精度浮点型字面量
CONSTANT_Class_info7类或接口的符号引用
CONSTANT_String_info8字符串类型字面量
CONSTANT_Fieldref_info9字段的符号引用
CONSTANT_Methodref_info10类中方法的符号引用
CONSTANT_InterfaceMethodref_info11接口中方法的符号引用
CONSTANT_NameAndType_info12字段或方法的符号引用
CONSTANT_MethodHandle_info15表示方法句柄
CONSTANT_MethodType_info16标志方法类型
CONSTANT_InvokeDynamic_info18表示一个动态方法调用点

(后三种是jdk1.7引入的)

常量类型的具体细节表:

在这里插入图片描述

在这里插入图片描述

(对CONSTANT_utf8_info的修正:length表示的字节数,bytes的u1表示的每个元素一个字节)

以一个字节码例子解释

在这里插入图片描述
在这里插入图片描述

0016是常量池计数器,转换为十进制为22,也就是常量池中有21个项。再往后面是常量池中的第一个项,每一项由若干部分组成,但第一部分都是tag,占一个字节(u1),该值是0a也就是10。

查表:在这里插入图片描述

10表示的是CONSTANT_Methodref_info,也就是方法的符号引用。再往后两个字节是该方法的所在的类的描述符在常量池中的索引,也就是0004,转换为十进制为4,也就是常量池中索引为4的一项,也就是070015那一项。接着继续查表7 在这里插入图片描述

表示类或接口的符号引用,再往后两个字节0015表示指向全限定名常量项的索引,转换成十进制为21,找常量池中的第21项也就是最后一项,也就是0100106a61一直到74那一项,第一个字节01转成十进制1,1查表在这里插入图片描述

表示的是U8编码的字符串,再往后两个字节表示该字符串的长度,也就是0010,转成十进制为16,也就是16个字符数,往后数16个字节,也就是从6a一直到74,6a转成十进制是106,106对应的ASC码是j,最终6a到74对应的字符串就是java/lang/Object,也就是对应的是Object这个类!!!

为什么该类class文件的常量池部分的第一项是Object的符号引用?

因为该类继承了Object,一个类没有显示继承,默认就会继承Object。当JVM加载一个类时,它需要知道该类的所有相关信息,包括其超类。所以会有Object的符号引用。

如果该类有了显示继承,那么第一项是其父类的符号引用,就没有了Object的符号引用。

4.4访问标识

紧跟着常量池的2个字节是访问标识,还以上面图为例,该字节码文件的访问标识就是0021
在这里插入图片描述

访问标识用于标识类或接口的访问信息,这个class是类还是接口,是不是public的,是否定义为abstract等等。各种访问标识如下表:

标志名称标志值含义
ACC_PUBLIC0x0001标志为public类型
ACC_FINAL0x0010标志被声明为final,只有类可以设置
ACC_SUPER0x0020标志允许使用invokespecial字节码指令的新语义,JDK1.0.2之后编译出来的类的这个标志默认为真。(使用增强的方法调用父类方法)
ACC_INTERFACE0x0200标志这是一个接口
ACC_ABSTRACT0x0400是否为abstract类型,对于接口或者抽象类来说,次标志值为真,其他类型为假
ACC_SYNTHETIC0x1000标志此类并非由用户代码产生(即:由编译器产生的类,没有源码对应)
ACC_ANNOTATION0x2000标志这是一个注解
ACC_ENUM0x4000标志这是一个枚举

上面的例子访问标识为0021,对应表就是ACC_PUBLIC和ACC_SUPER的和(注意:访问标识是相加的结果)。

补充说明:

  1. ACC_SUPER,现代编译器都会设置并使用这个标记,可以认为是默认带的。

  2. 带有ACC_INTERFACE则是接口,不带则是类。

  3. 带有ACC_ANNOTATION的是注解类型。如果设置了ACC_ANNOTATION,则必须也得有ACC_INTERFACE。

在jclasslib中访问标识在这查看
在这里插入图片描述

4.5类索引、父类索引、接口计数器、接口索引集合

紧跟着访问标识的依次是类索引、父类索引、接口计数器、接口索引集合,都是占2个字节。

长度含义
u2this_class
u2super_class
u2interfaces_count
u2interfaces[interfaces_count]

在上面的例子中,类索引、父类索引、接口计数器依次是0003、0004、0000,由于接口计数器为0,所以也就是实现了0个接口,接口所以集合也就没有了。类索引、父类索引、接口索引集合,都是索引,常量池的索引,也都是在常量池中找。比如0003是常量池中索引为3的项:070014,查表,表示的是类或接口的符号引用,再用14(转换为十进制为20)定位查表,表示的01001663一直到6f,表示的是字符串,转换为ACS码就可以了。

4.6字段表

紧跟着接口索引集合的是字段表计算器和字段表。字段表用于描述接口或类中声明的字段(fields),字段包括类变量(static修饰的变量)、实例变量(非statci修饰的变量)以及final修饰的常量,但不包括方法内部的局部变量。

字段的名字、数据类型、访问修饰符都在字段表里,但也都是通过引用常量池中的项来描述的,字段表里也都是存储的常量池的索引。

注意:字段表集合中不会列出从父类或接口继承来的字段,但也可能列出原本Java代码中不存在的字段,例如在内部类中为了保持对外部类的访问性,会自动添加指向外部类的实例字段。

只要是个表就会先有个计数器,字段表也有字段计数器(fields_count)。用来描述字段表中字段的个数,使用两个字节来存储。字段表中的每个成员都是一个field_info结构。

field_info结构

类型名称含义数量
u2access_flags访问标志(具体见下表)1
u2name_index字段名索引1
u2descriptor_index描述符索引1
u2attributes_count属性计数器1
attribute_infoattributes属性集合attributes_count

字段表访问标志

标志名称标志值含义
ACC_PUBLIC0x0001字段是否为public
ACC_PRIVATE0x0002字段是否为private
ACC_PROTECTED0x0004字段是否为protected
ACC_STATIC0x0008字段是否为static
ACC_FINAL0x0010字段是否为final
ACC_VOLATILE0x0040字段是否为volatile
ACC_TRANSTENT0x0080字段是否为transient
ACC_SYNCHETIC0x1000字段是否为由编译器自动产生
ACC_ENUM0x4000字段是否为enum

字段名索引和描述符索引也都是在常量池中找。(描述符忘了是什么看一下前面的介绍)

字段的属性用于存储更多的额外信息,比如初始化值、一下注释信息。以常量属性为例,结构为:

ConstantValue_attribute{

​ u2 attribute_name_index;

​ u4 attribute_length;

​ u2 constantvalue_index;

}

对于常量属性而言,attribute_length值恒为2。

在jclasslib中查看字段表:
*在这里插入图片描述

4.7方法表

紧跟着字段表的是方法表。方法表和字段表的套路差不多,也包括计数器和具体的表,只不过描述符会有些区别。

方法表:指向常量池索引的一个集合,完整描述了每个方法的签名。

每一个method_info项都对应着类或接口中的方法信息,比如方法的访问修饰符、返回类型、参数信息。

和字段表一样,一方面,方法表也只描述当前类或接口中声明的方法,不包括从父类或接口继承来的方法;另一方面,方法表也可能会自动列出由编译器自动添加的方法,最典型的就是类初始化方法<clinit>()和实例化方法<init>(),init就是构造器方法,如果没有显示定义类构造器,编译器会自动添加一个无参构造器,这一点在方法表中就能直接看到。

方法表计数器也用一个u2类型两个字节表示,方法表的结构和字段表也是相似的:

类型名称含义数量
u2access_flags访问标志(具体见下表)1
u2name_index方法名索引1
u2descriptor_index描述符索引1
u2attributes_count属性计数器1
attribute_infoattributes属性集合attributes_count

方法表访问标识

标志名称标志值含义
ACC_PUBLIC0x0001public,方法可以从包外访问
ACC_PRIVATE0x0002private,方法只能从本类中访问
ACC_PROTECTED0x0004protected,本类、子类、同包中可访问
ACC_STATIC0x000static
ACC_FINAL0x0010为final
ACC_VOLATILE0x0040volatile
ACC_TRANSTENT0x008transient
ACC_SYNCHETIC0x1000是否为由编译器自动产生
ACC_ENUM0x4000enum

每个方法也可以有属性,例如init方法的Code属性
在这里插入图片描述

每个属性也是一个attribute_info结构,也是一个表,结构如下:

类型名称数量含义
u2attribute_name_index1属性名索引
u4attribute_length1属性长度
u1infoattribute_length属性表

具体Code属性也有自己的格式(再次套娃):

在这里插入图片描述

Code属性中含有字节码指令,但是字节码中都是2a等等这样的16进制数字,怎么和aload_0这样的字节码指令对应起来的?

事实上,你去点一下jclasslib中的字节码

在这里插入图片描述

点击’显示JVM规范’会直接跳到oracle官网,可以看到0x2a和具体的aload_0这样的字节码指令的对应关系!!

在这里插入图片描述

这样,16进制的字节码也就翻译成了aload_0这样的字节码指令了。

再看上面Code属性的结构,会发现Code属性也有自己的属性(再次套娃),再jclasslib中点开Code属性:
在这里插入图片描述
就是LineNumberTable和LocalVariableTable两个属性。

不再往下继续查表了。。。

4.8属性表

紧跟着方法表的是属性表,指的是class文件所带的辅助信息,比如class文件的源文件的名称,一般无须深入了解。

5.总结

翻译字节码的过程其实就是拿着字典查字典的过程,一些描述信息都是在常量池中存储着,常量池中的CONSTANT_utf8_info类型,转成十进制,再对应ASC码表,就是我们在Java源代码中定义的东西,如String str中的str。

最后附上一张字节码的完全解析:
在这里插入图片描述

相关文章:

  • 101个α因子#25
  • android:exported=“true“的作用
  • 【DAY26】函数专题1:函数定义与参数
  • 软考高项考前48小时冲刺:核心考点记忆 + 错题复盘 + 3 科重点
  • 先进先出(FIFO)页面置换算法
  • CentOS7安装 PHP-FPM 7.4
  • git@gitee.com: Permission denied (publickey). fatal: 无法读取远程仓库
  • 基于单片机的室内采光及可燃气体泄漏报警装置设计
  • FastAPI在 Nginx 和 Docker 环境中的部署
  • 深入理解线程池:参数、流程与实战应用
  • Qt控件:输入控件
  • 地下水监测的施工与安装
  • 人工智能时代:从“知识容器”到“知识地图”的认知革命
  • 华为模拟器练习简单的拓扑图(2台三层交换机和8台pc)
  • 【知识图谱】数据处理与数据存储
  • 用对称化与chaining技术bound经验过程上确界的期望(Guntuboyina理论统计学笔记)
  • Three.js搭建小米SU7三维汽车实战(1)搭建开发环境
  • vue3定于组件名字的几种方法
  • 浙江大学python程序设计(陈春晖、翁恺、季江民)习题答案-第十章
  • QT ui控件setEnabled(false) 作用
  • 做电工的有接单的网站吗/电子商务营销方法
  • 十堰优化网站公司/网址查询域名解析
  • 盐城市住房城乡建设网站/seo编辑招聘
  • 一流学科建设专题网站/北京昨晚出什么大事
  • wordpress 插件 下载/潍坊关键词优化排名
  • 做智能网站系统/高平网站优化公司