5分钟系列之-Java类文件结构(二、字节码分析)

2.Class文件的结构

  class文件中各数据项严格按照顺序紧凑的排列在class文件中,没有空隙存在,各项的排列顺序如图。

在这里插入图片描述

表1
模板常常问类父,计数集合顺序数,接口字段方法加,简简单单记住啦 这种数据结构只有两种类型:无符号数和表

2.1 无符号数

  无符号数是属于基本的数据类型,以u1、u2、u4、u8来分别代表1个字节、2个字节、4个字节和8个字节的无符号数,无符号数可以用来描述数字、索引引用、数量或者按照UTF-8编码构成字符串值。

2.2 表

  表是由多个无符号数或者其他表作为数据项构成的复合数据类型。表用于描述有层次关系的复合数据类型。

2.3 解读字节码文件

  我们以下边这个类为范例讲解下类的字节码解析过程

public class Simple {
    int a=1;
    int add(int b){
        return a+b;
    }
}
2.3.6访问标志

  常量池过后紧跟着的是u2的访问标志,用于识别以下类或接口层次的访问信息,包括这个class是类还是接口;是否定义为public类型等,具体的标志位含义如下图:

在这里插入图片描述
我们来解读下本例中的访问标志位

在这里插入图片描述

其值为0x0021,表示的是0x0020与0x0001的并集,也就是一个public的类

2.3.7类索引、父类索引与接口索引

  类索引、父类索引都是一个u2类型的数据,接口索引是一组u2类型的的数据集合,由于是一组,因此这组数据前需要有个u2的标志为记录这个数组的大小,这也体现了Java的类是单继承,而接口可以为多继承的;
类索引和父类索引各自指向一个类型为CONSTANT_CLass_info的常量,此处指向了常量池中的第三和第四项(这里也勘正下上节课笔误写错的地方,也就是2.3.5中第三第四项解析时写的指向常量池中的第11和第12,由于时16进制,此处应该时17,18项),解析过程如下图,红框为类索引与父类索引

在这里插入图片描述

看第17与第18项如下图分别为Simple,和java/lang/Object
在这里插入图片描述

接下来的u2为接口计数器,其值为0,表示后边的接口索引表不再占用任何字节。

在这里插入图片描述

2.3.8字段计数器与字段集合

  接下来是u2类型的字段个数,此处为1个,
在这里插入图片描述

字段包含类级变量与实例级变量,其表结构如下图

在这里插入图片描述

表2-3-8-1

变量也有自己的修饰符,比如public,private,final等,表2-3-8-1中第一项就是,其具体可以参照下图
在这里插入图片描述

按表2-3-8-1划分,其中第一部分为访问修饰符 此处为0x0000表示没有写;第二部分为字段名称索引,此处为0x0005表示指向常量池中的第五项;第三部分为描述符索引,此处值为0x0006,表示指向常量池中第六项;第七项为属性计数器,此处值为0x0000表示后边没有属性。具体分析如下图。

在这里插入图片描述

讲到这里有必要解释一下简单名称和描述符的概念

简单名称

  表2-3-8-1中第二项字段名索引代表着字段的简单名称,简单名称就是没有类型和修饰符的方法或者字段名称,比如int a,其简单名称就是a;

描述符

  表2-3-8-1中第三项代表的是描述符, 描述符的作用是描述字段的数据类型、方法的参数列表和返回值,其对对应关系如下图

在这里插入图片描述

  可以看到第三项对应的是I表示int类型,与代码中的一致,此两项解析完就是 a I ,简单名称在前,类型在后。

  对于数组类型,每一维度将使用一个前置的“[” ,如定义一个java.lang.String[][]的二维数据将被记为[[Ljava/lang/String,用来描述方法时,按照先参数列表后返回值的顺序描述,参数列表按照参数顺序放在小括号“()”内。如方法

int indexOf(char[] source,int sourceOffSet,int sourceCount,char[] target,int targetOffset)

将被描述为
([CII[CI)I

2.3.9方法计数器与方法集合

  字段集合后紧跟着的就是一个u2类型方法计数器,表示方法的个数,本例中为0x0002表示有两个方法(其中一个是构造方法)
在这里插入图片描述

有了上边解析字段的经验,解析方法简单的多,先上方法表结构图
在这里插入图片描述

2-3-9-1
同样道理 ,方法也有访问标志,如下图

在这里插入图片描述

第一项access_flags,访问标志为0x0001,表示为public
第二项name_index方法名称索引为0x0007,指向常量池中的第七项
第三项descriptor_index描述符为0x0008,指向常量池中的第八项
第四项attributes_count属性计数器为0x0001,表示有一个形参属性
属性计数器面紧接着的就是属性表,属性表第一个是u2类型的属性名称索引,这里的值为0x0009,查下常量池中的第9项,属性名称为Code
在这里插入图片描述

2.3.10属性表

  属性类型

在这里插入图片描述

属性类型可以有很多,Code只是其中的一种

属性表结构

  属性表的前两项都是固定的,按顺序为属性索引,属性长度,

在这里插入图片描述

只需说明属性的名称以及占用位数的长度即可,属性表具体的结构可以去自定义。
下边看下Code属性表结构
在这里插入图片描述

第一项attribute_name_index,其值为Code,上边已经解读过
第二项attribute_length属性值的长度0x00000026,即长度为38,即此后38为为属性表的长度
第三项max_stack的值为0x0002,即操作数栈深度的最大值为2
第四项max_locals的值为0x0001,即局部变量表所需的存储空间为1
第五项code_length字节码执行长度为0x0a,为10
第六项code的值为2a b7 00 01 2a 04 b5 00 02 b1 ,这里的值就代表一系列的字节码指令。一个字节代表一个指令,一个指令可能有参数也可能没参数,如果有参数,则其后面字节码就是他的参数;如果没参数,后面的字节码就是下一条指令
指令助记符参考:https://docs.oracle.com/javase/specs/jvms/se15/html/jvms-7.html


2a 指令,查表可得指令为aload_0,其含义为:将第0个Slot中为reference类型的本地变量推送到操作数栈顶。
b7 指令,查表可得指令为invokespecial,其含义为:将操作数栈顶的reference类型的数据所指向的对象作为方法接受者,调用此对象的实例构造器方法、private方法或者它的父类的方法。其后面紧跟着的2个字节即指向其具体要调用的方法。
00 01,指向常量池中的第1项,查询上面的常量池可得: #1 = Methodref #4.#15 // java/lang/Object."":()V 。即这是要调用默认构造方法。
2a 指令,同第1个。
04 指令,查表可得指令为iconst_1,其含义为:将int型常量值1推送至栈顶。
b5 指令,查表可得指令为putfield,其含义为:为指定的类的实例域赋值。其后的2个字节为要赋值的实例。
00 02,指向常量池中的第2项,查询上面的常量池可得: #2 = Fieldref #3.#16 // Simple.a:I。即这里要将num这个字段赋值为1。
b1 指令,查表可得指令为return,其含义为:返回此方法,并且返回值为void。这条指令执行完后,当前的方法也就结束了。

第七项exception_table_length的值为0x0000,即异常表长度为0;
第八项exception_table异常表长度为0
第九项attributes_count的值为0x0001,即code属性表里面还有一个其他的属性表,后面就是这个其他属性的属性表了;
第一个属性表解析完了,过程具体如下图

在这里插入图片描述

第一个属性表的最后一部分指明了接下来为一个新的属性表,按规则前两项是固定的,那么第一部分也为u2类型的属性名称,其值为0x000a 即为10指向常量池中的第十项,其值为 LineNumberTable

LineNumberTable

  LineNumberTable属性是用来描述Java源码行号与字节码行号之间的对应关系。
LineNumberTable属性表

在这里插入图片描述

第一项attribute_name_index值为0x000a 即为10指向常量池中的第十项(上边说过了)
第二项attribute为0x0000000a,此项为属性长度,表示后面10个字节的都是LineNumberTable属性的内容;
第三项line_number_table_length为0x0002,即其行号表长度长度为2,即有两个行号表;
第四项line_number_info类型(行号表),其长度为4个字节,前两个为 start_pc,即字节码行号;后两个为line_number,即Java源代码行号

在这里插入图片描述

解析了这么多实际上就是下边这个构造方法
在这里插入图片描述

解析第二个方法

  解析完第一个后,按照同样的方式可以解析第二个方法,也就是我们写的add方法,也顺带当做练习,按表2-3-9-1拆分,先拆分前四个
第一项access_flags访问标志为0表示没有写
第二项name_index方法名称索引为0x000b,指向常量池中的第11项
第三项descriptor_index描述符为0x000c,指向常量池中的第12项
第四项attributes_count属性计数器为0x0001,表示有一个形参属性

在这里插入图片描述

接下来解析属性表

在这里插入图片描述

第一项attribute_name_index,其值为Code,上边已经解读过
第二项attribute_length属性值的长度0x0000001f,即长度为31,即次后31长度为属性表的长度
第三项max_stack的值为0x0002,即操作数栈深度的最大值为2
第四项max_locals的值为0x0002,即局部变量表所需的存储空间为2
第五项code_length字节码执行长度为0x07,为7
第六项code的值为2a b4 00 02 1b 60 ac
第七项exception_table_length的值为0x0000,即异常表长度为0;
第八项exception_table异常表长度为0
第九项attributes_count的值为0x0001,即code属性表里面还有一个其他的属性表,后面就是这个其他属性的属性表了;
属性表第一个u2为0x000a,表示LineNumberTable属性,
按照LineNumberTable属性表解析

在这里插入图片描述

第一项attribute_name_index值为0x000a 即为10指向常量池中的第十项(上边说过了)
第二项attribute为0x00000006,此项为属性长度,表示后面6个字节的都是LineNumberTable属性的内容;
第三项line_number_table_length为0x0001,即其行号表长度长度为1,即有1个行号表;
第四项line_number_info类型(行号表),其长度为4个字节,前两个为 start_pc,即字节码行号;后两个为line_number,即Java源代码行号,我们在debug代码时,以及发生异常进行打印时都是使用这些行号
在这里插入图片描述

2.3.11附加属性
附加属性计数器

  接下来的两位是附加属性计数器,0x0001表示后边有一个附加属性
在这里插入图片描述

附加属性

  附加属性的文件结构如下
在这里插入图片描述

第一项attribute_name_index属性名称值为0x000d,执行常量池中第13项,也就是 SourceFile;
第二项attribute_length属性长度值为0x00000002,也就是2,表示后边2位为属性值的长度;
第三项sourcefile_index源码文件索引值为0x000e,也就是常量池中的第14项,即 Simple.java
在这里插入图片描述

至此本java类的字节码文件就解读完了。

属性表集合

  属性表在前边的讲解中出现了很多此,在class文件,字段表,方法表都可以携带自己的属性表集合,以用于描述某些场景专有的信息,属性表集合的限制稍微宽松了一些,不再要求各个属性表具有严格的顺序,并且只要不与已有的属性名重复,任何实现的编译器都可以向属性表中写入自己的属性信息,java虚拟机运行是会忽略掉它不认识的属性。在最新的java虚拟机规范中(Java SE 15 )属性已经增加到28项。

参考链接:https://docs.oracle.com/javase/specs/jvms/se15/html/jvms-4.html#jvms-4.7-300
每天5分钟,您看着不累!剩余部分解析内容下节分享!


公众号同步更新,欢迎订阅

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值