一步一步拆解字节码(.class)文件
为了方便理解,本篇博客将围绕一个例子展开
目录
二. minor version(次版本号)和major_ version(主版本号)
三. constant pool count(常量个数)和constant pool(常量池表)
五. this_ class(类名)和super_ class(父类名)
六. interfaces_count(接口个黎)和interfaces(接口名)
七. fields_ count(域个数)和fields(域的表)
八. methods_ count(方法的个数)和methods(方法表)
九. attributes_ count(附加属性的个数)和attributes(附加属性的表)
例 day02.ByteCode.java:
IDEA中自带查看字节码信息工具,该字节码文件的魔数、版本号、常量池、类信息、类的构造方法、 类中的方法信息、类变量与成员变量等信息。
如果想要查看其十六进制字节码文件,需要下载WinHex工具,用它打开:
接下来咱们详细分析这些二进制到底是什么东西。
字节码中携带信息顺序如下图(类型中un代表占位n个字节,类型中包含info的,说明该类型中包含许多字段信息,会有相应的表描述这些字段):
一. magic(魔数)
所有合法的.class字节码文件的前4个字节都是魔数,魔数值为固定值: 0xCAFEBABE,即咖啡宝贝之意。
二. minor version(次版本号)和major_ version(主版本号)
魔数之后的4个字节为版本信息,前两个字节表示minor version (次版本号) ,后两个字节表示major version (主版本号) .这里的版本号为00 00 00 34,换算成十进制,表示次版本号为0,主版本号为52。52对应的jdk版本号为: 1.8.0,同理可推51对应1.7.0。 可以通过java -version命令来验证这一 点。
三. constant pool count(常量个数)和constant pool(常量池表)
紧接着主版本号之后的就是常量池入口。一个Java类中定义的很多信息都是由常量池来维护和描述的,可以将常量池看作是Class文件的资源仓库,比如说Java类中定义的方法与变量信息,都是存储在常量池中。常量池中主要存储两类常量:字面量与符号引用。字面量如文本字符串,Java中声明为final的常量值等,而符号引用如类和接口的全局限定名,字段的名称和描述符,方法的名称和描述符等。
常量池的总体结构: Java类所对应的常量池主要由常池数量与常量池数组(常量表)这两部分共同构成。 常量池数量紧跟在主版本号后面,占据2个字节;常量池数组则紧跟在常量池数量之后。常量池数组与般的数组不同的是, 常量池数组中不同的元素的类型、结构都是不同的,长度当然也就不同;但是,每种元素的第一个数据都是一个u1类型,该字节是个标志位,占据1个字节。JVM在解析常量池时,会根据这个u1类型来获取元素的具体类型。值得注意的是,常量池数组中元素的个数=常量池数- 1 (其中0暂时不使用),目的是满足某些常池索引值的数据在特定情况下需要表达 不引用任何个常量池的含义;根本原因在于, 索引为0也是个常量(保留常量) ,只不过它不位于常量表中,这个常量就对应nu11值;所以,常池的索引从1而非0开始。
图 3-1 常量查询参照表
常量池长度:00 18 即常量个数有24-1 = 23个
常量1:
0A 00 04 00 14,第一个字节时tag值,0A = 10,所以tag = 10,对应CONSTANT_Methodref_info常量
该常量有三部分组成:tag(U1),index(U2),index(U2),后面是Un就代表占n个字节
所以声明方法的类描述符(结合图3 -1)的索引值为 00 04即为4
名称及类型描述符(结合图3 - 1)的索引值为00 14即为20
该常量的值就等于常量4与常量20的值“相加”。
常量2:
tag值为09,在图3-1中查询tag等于9,即CONSTANT_Fieldref_info常量
后面的第一个index字段占位2个字节,00 03,因为是index类型所以,找到的3是常量在常量池中的位置
同理第二个index字段是00 15,该常量是第三个常量和第二十一个常量相加的结果
需要注意的是如果类型为index指的是常量池中的常量位置,如果是byte,那么将相应bytes转换为英文字母即为结果
同理二十三个常量如下图:
四. access_ flags(类的访问控制权限)
图 4-1 访问控制权限图
直接查表,00 21查表得类的访问权限为public super;
JDK1.2之后编译出来的类的super标志都必须为真。
五. this_ class(类名)和super_ class(父类名)
类名:00 03即查找下标为3的常量,即本类day02/ByteCode
父类名:00 04即java/lang/Object
六. interfaces_count(接口个黎)和interfaces(接口名)
接口个数:00 00,所以接口名不占空间
七. fields_ count(域个数)和fields(域的表)
域个数:00 01 只有一个成员变量:
域的表:
访问控制类型:00 02通过图 4-1 访问控制权限图查询即private类型
变量名下标:00 05,在常量池中查找即a
变量描述信息:00 06,在变量池中查找即I
变量附加信息个数:00 00
所以attribute_info去域为空,不占位
八. methods_ count(方法的个数)和methods(方法表)
方法个数:00 03即三个,分别是默认构造方法,get方法,set方法
方法1(构造方法):
访问权限控制符(access_flag):00 01 即public类型
方法名下标(name_index):00 07 ,在常量池中找下标为7的常量,即<init>,它的意思是代表构造方法
描述下标(descriptor_index):00 08,即()V,代表无参且返回值为void类型
属性个数(attributes_count):00 01代表只有一个
属性信息:
属性信息中,前两个字节代表属性名在常量池中的下标,再根据属性名查相应的表
属性名下标:00 09,查找得:Code,下图是Code属性表相关信息
atrribute_name_index已经查找过了
属性信息长度:即不包括attribute_name_index和atrribute_length的Code_attribute总长度,00 00 00 38计算得共占56个字节
maxstack:表示这个方法运行的任何时刻所能达到的操作数栈的最大深度,00 02计算得最大深度为2
max_locals:表示方法执行期间创建的局部变量的数目,包含用来表示传入的参数的局部变量,00 01即共创建1个参数
code_length:code length表示该方法所包含的字节码的字节数以及具体的指令码,00 00 00 0A即长度为10个字节
code[code_length]:该部分包含了一些助记符,形式如下:
2A:代表aload_0,下面是官网文档,代表将其推至栈顶,官网地址
同理:
B7:代表invokespecial,00 01是它携带的参数在常量池中的位置:
是父类的构造方法,官网地址
2A:aload_0 ,官网地址
04:iconst_1,官网地址
B5:putField,后面的00 02是常量池中的位置,day02/ByteCode.a:I,官网地址
B1:return,官网地址
该方法的意思是:在Object的构造方法中将day02/ByteCode中的成员变量a赋值为1
exception_table_length:异常表的长度,这里没有异常处理,所以为00 00,所以exception_table字段不会显示了
attributes_count:00 02,就是两个属性表
attribute_info表中数据各字段如下:
先看第一个:attribute_name_index:00 0A在常量池中查找得:LineNumberTable即行号表,格式如下:
这里要说明一下:attribute_info表是LineNumberTable_attribute表的父表,字表中把父表的info字段实例化啦
接着看:
attribute_length:00 02即其中只有两个属性
第一个:start_pc:偏移量,00 00,line_number:实际行号为00 03
第二个:start_pc:偏移量,00 04,line_number:实际行号,00 04
因为现在还在构造方法中,在编译时,会自动在类名后创建构造方法,所以构造方法在第三行,又因为a在第四行定义赋值,所以第二个属性实际行号是4
再看第二个:attribute_name_index:00 0B,在常量池中查找第11个变量得:LocalVariableTable,详情见下表,
attribute_length:00 00 00 0C:长度为12,指的是局部变量表长度字段(U2)和局部变量表的总长
local_variable_table_length:00 01说明只有一个变量,你可能觉得构造方法中没有定义局部变量,但是请你接着看
这一个变量的local_variable分为五部分:
start_pc:程序计数器起始值,00 00
length:索引长度,00 0A,长度为10
name_index:名称索引,00 0C,查常量池得this
descriptor_index:描述符索引,00 0D,查常量池得Lday02/ByteCode;即this是day02.ByteCode类型的对象
index:变量下标,00 00即第0个
这里可以看出每个非静态方法中都隐式传入一个this作为局部变量以供使用
其它两个方法亦然;这里不做赘述。
九. attributes_ count(附加属性的个数)和attributes(附加属性的表)
attributes_count:00 01代表只有1个属性
attribute_index:00 12,在常量池中查找,得SourceFile,源文件
attribute_file_length:00 00 00 02,表示文件位置下标占位2个字节
attribute_file_index:00 13,在常量池中,查找得ByteCode.java
表示有源文件是ByteCode.java。
用IDEA的可以添加一个jclasslib插件,方便使用
第一次写那么长的文章,错误之处,请多批评指教。