什么是class文件
JAVA字节码文件(.class文件)是java编译器编译java源文件(.java文件)所产生的目标文件。它是一种8位字节的二进制流文件,各个数据项按顺序紧密的从前向后排列,相邻的项之间没有间隙,这样使得class文件非常紧凑,体积轻巧,可被JVM快速加载到内存,并且占据较少的内存空间。理解字节码其实就是了解JVM是如何解析字节码的。JVM能够解析并执行字节码文件,而不关心字节码是由哪种语言编译而来的,所以理论上JVM还可以支持除了JAVA以外的其他能够编译生成字节码文件的语言,如Groovy,JRuby,Jython等,只要安装了虚拟机的平台(无论是windows,linux,还是mac),都可以直接运行字节码.

class文件结构

我们可以认为class文件只有两种数据结构:无符号数和表。

无符号数:属于基本的数据类型,以u1,u2,u4,u8来分别代表1个字节,2个字节,4个字节,8个字节的无符号数。

表:表是由多个无符号数或者其他表作为数据项构成的复合数据类型,class文件中所有的表都以"_info"结尾。其实,整个class文件本质上就是一张表。

一个典型的class文件结构会按照预先规定好的顺序紧密的从前向后排列,相邻之间没有间隙。如下图所示(该图很重要,最好能倒背如流):

java字节码文件 JAVA字节码文件名_常量池


当JVM加载某个class文件时,JVM就是根据上图中的结构去解析class文件,在强调一遍(那顺序是不会改变的,严格按照上图给出的顺序),加载class文件到内存中,并在内存中分配相应的的空间。具体某一种结构需要占用多大空间,可以参考下图(该图也很重要):

java字节码文件 JAVA字节码文件名_字符串_02


实例分析

看十遍不如自己操作一遍,所以最好还是自己码一遍,加深理解.

首先编写一个简单的Java源代码 ByteCodeTestInRoyole.java,如下所示:

package com.example.helloroyole;
 
import java.io.Serializable;
 
/**
 * ClassName ByteCodeTestInRoyole
 * description
 *
 * @author Ace.Huang@royole.com
 * @date 2020/12/08
 */
public class ByteCodeTestInRoyole implements Serializable,Cloneable {
    private static final long serialVersionUID = -2793625091785878343L;
    private static final String KEY_SUMMARY = "color_temperature_title_key";
    private int count;
 
    private int add(int i,int y){
        return i + y;
    }
 
}

通过javac命令将其编译:

java字节码文件 JAVA字节码文件名_jvm_03


生成ByteCodeTestInRoyole.class字节码文件:

java字节码文件 JAVA字节码文件名_jvm_04


如果使用notepad++打开的话,会显示乱码,需要安装插件,我使用了Beyond Compare也可以打开,如下图所示(这图在以下说明中会反复提到):

java字节码文件 JAVA字节码文件名_java_05


上图都是一些16进制数字,每两个字符代表一个字节。如果是第一次看,会感觉各个字符之间毫无规律,跟乱码差不多,但其实在JVM的视角里这些16进制字符是按照严格的规律排列的。接下来就结合写的demo源码看下JVM是如何解析它们的。

我按照class文件结构图一个个来,首先是:

魔数 magic number(至于为什么叫魔数,我不知道…只是个叫法而已,无须纠结)

在class文件中开头的四个字节就表示该class文件的魔数,它是一个固定的值,0XCAFEBABE。魔数是class文件的标志,用来判断一个文件是不是class格式文件的标准,如果开头四个字节不是0XCAFEBABE,那么就说明它不是class文件,不能被JVM识别或加载。

版本号

版本号也是占4个字节,也就是紧跟在魔数后面的4个字节。前两个字节0000表示副版本号minor_version,后两个字节0034是主版本号major_version,对应的十进制值为52,也就是说当前class文件的主版本号为52,副版本号为0,所以综合版本号是52.0,这里说的版本号是指JDK的版本号,也就是jdk1.8.0。附上一张jdk版本对应的class文件的版本号:

java字节码文件 JAVA字节码文件名_java字节码_06


常量池(重点,重中之重)constant_pool

紧跟在版本号之后的是一个叫做常量池的表(cp_info)。在常量池中保存了类的各种相关信息,比如类的名称,父类的名称,类的方法名,参数名称,参数类型等,这些信息都是以各种表的形式保存在常量池中的。

常量池中的每一项都是一个表,其项目类型共有14种,如下图所示:

java字节码文件 JAVA字节码文件名_java字节码_07


以上14种表都有自己的结构,每一种结构可参考如下总表:

java字节码文件 JAVA字节码文件名_字符串_08


从表名可以得出其数据类型,比如,CONSTANT_utf8_info表,其数据类型就是CONSTANT_Utf8,常量池中的每一项都会有一个u1大小的tag值。tag值是表的标识,JVM解析class文件时,通过这个值来判断当前数据结构是哪一种表。

例如CONSTANT_Class_info表具体结构如下:

table CONSTANT_Class_info{
u1 tag = 7;
u2 name_index;
}

tag:占用一个字节,比如值为7,根据上图,说明是CONSTANT_Class_info类型表。

name_index:是一个索引值,可以将它理解为一个指针,指向常量池中索引为name_index的常量表。比如name_index = 2,则它指向常量池中第2个常量。

再比如CONSTANT_Utf8_info表具体结构如下:

table CONSTANT_Utf8_info{
u1 tag;
u2 length;
u1[] bytes;
}

tag:值为1,表示是CONSTANT_Utf8_info类型表。

length:length表示u1[]的长度,比如length = 5;则表示接下来的数据是5个连续的u1类型数据。

bytes:u1类型数组,长度为上面第2个参数length的值。

了解了这些概念后,紧接着我们上面字节码的分析,跟在版本号后是常量池,常量池的大小constant_pool_count占2个字节,即demo中的001e,即十进制30,其中下标为0的常量被JVM留作其他特殊用途,所以字节码常量池是从1开始计数的,实际中常量池中的大小为该值减1,也就是在本demo中有29个常量.

第一个常量,也就是紧接着常量池大小的一个字节0A,转为十进制后为10,通过查看常量池14种表格可知,10对应的表类型为CONSTANT_Methodref_info,其结构如下:

table CONSTANT_Methodref_info {
u1 tag = 10;
u2 class_index;  //指向此方法所属的类在常量池的索引
u2 name_type_index; //指向此方法的名称和类型的索引
}

也就是说在0A之后的2个字节0003指向这个方法是属于哪个类,紧接的2个字0018节指向这个方法的名称和类型。即:

0003:十进制3,表示指向常量池中的第3个常量。

0018:十进制24,表示指向常量池中的第24个常量。

至此,第一个常量就解读完了,紧接着的就是第二个常量,07,转为十进制后为7,同理通过查看常量池表14种表可知,7对应的表类型为CONSTANT_Class_info,该结构已经给出,接下来的2个字节0019指向这个类的名称。即:

0019:十进制25,表示指向常量池中的第25个常量。

第三个常量,跟在0019后面的第三个常量是07,同第二个常量一样,对应的表类型为CONSTANT_Class_info,接下来的2个字节001A指向这个类的名称。即:

001A:十进制26,表示指向常量池中的第26个常量。

以此类推,第四个常量,即跟在001A后面的一个字节07,对应的表类型也为CONSTANT_Class_info,接下来的2个字节001B指向这个类的名称。即:

001B:十进制27,表示指向常量池中的第27个常量。

第五个常量,即跟在001B后面的一个字节也是07,对应的表类型也为CONSTANT_Class_info,接下来的2个字节001C指向这个类的名称。即:

001C:十进制28,表示指向常量池中的第28个常量。

第六个常量,即跟在001C后面的一个字节01,转为十进制后为1,查看常量池14种表可知,1对应的表类型为CONSTANT_Utf8_info,重复一遍该结构如下:

table CONSTANT_Utf8_info{
u1 tag;
u2 length;
u1[] bytes;
}

也就是说在01之后的两个字节0010,表示这个字符串的长度是16字节,也就是后面的16个字节73 65 72 69 61 6C 56 65 72 73 69 6F 6E 55 49 44,通过ASCII码表转换后,表示的是字符串“serialVersionUID”。即:

0010:十进制16,表示该字符串的长度就是16字节。

73 65 72 69 61 6C 56 65 72 73 69 6F 6E 55 49 44:通过ASCII码表转换后,表示的是字符串“serialVersionUID”。

第七个常量,即跟在73 65 72 69 61 6C 56 65 72 73 69 6F 6E 55 49 44后面的是01,对应的表类型也是CONSTANT_Utf8_info,同理,接下来的2个字节00 01表示字符串的长度为1字节,后面跟着的1字节4A,通过ASCII码转换后,表示的是字符串“J”。即:

0001:十进制1,表示该字符串的长度为1字节。

4A:通过ASCII码转换后,表示的是字符串“J”。

第八个常量,即跟在4A后面的是01,对应的表类型也是CONSTANT_Utf8_info,同理,接下来的2个字节00 0D表示字符串的长度为13字节,后面跟着的13字节43 6F 6E 73 74 61 6E 74 56 61 6C 75 65,通过ASCII码转换后,表示的是字符串“ConstantValue”。即:

000D:十进制13,表示该字符串的长度为13字节。

43 6F 6E 73 74 61 6E 74 56 61 6C 75 65:通过ASCII码转换后,表示的是字符串“ConstantValue”。

第九个常量,即跟在43 6F 6E 73 74 61 6E 74 56 61 6C 75 65后面的是05,对应的表类型为CONSTANT_Long_info,由结构总表可知,接下来的8个字节D9 3B 0C C7 C0 7C 14 B9表示长整型:-2793625091785878343L。

备注1:此处有点蛋疼,先埋个雷,自己挖坑往里跳。
第十个常量(其实是第十一个),即跟在D9 3B 0C C7 C0 7C 14 B9后面的是01,对应的表类型为CONSTANT_Utf8_info,根据结构表,接下来的2个字节000B表示字符串的长度为11字节,则后面跟着的11字节4B 45 59 5F 53 55 4D 4D 41 52 59,通过ASCII码转换后,表示的是字符串“KEY_SUMMARY”。即:

000B:十进制11,表示该字符串的长度为11字节。

4B 45 59 5F 53 55 4D 4D 41 52 59:通过ASCII码转换后,表示的是字符串“KEY_SUMMARY”。

第十一个常量(其实是第十二个),01,表示对应的表类型为CONSTANT_Utf8_info,根据结构表,接下来的2个字节0012表示字符串的长度为18字节,则后面跟着的18字节4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B,通过ASCII码转换后,表示的是字符串“Ljava/lang/String;”。即:

0012:十进制18,表示该字符串的长度为18。

4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B:通过ASCII码转换后,表示的是字符串“Ljava/lang/String;”。

第十二个常量(其实是第十三个),08,表示对应的表类型为CONSTANT_String_info,根据结构表,接下来的2个字节001D表示指向字符串字面量的索引。即:

001D:十进制29,表示指向常量池中的第29个常量。

第十三个常量(其实是第十四个),01,这应该很熟悉了,对应的表类型为CONSTANT_Utf8_info,接下来的2字节0005表示字符串的长度为5字节,则后面跟着的5字节63 6F 75 6E 74,通过ASCII码转换后,表示的是字符串“count”。

第十四个常量(其实是第十五个),01,表类型CONSTANT_Utf8_info,接下来的2字节0001表示字符串的长度为1字节,则后面跟着的1字节49,通过ASCII码转换后,表示的是字符串“I”。

第十五个常量(其实是第十六个),01,表类型CONSTANT_Utf8_info,接下来的2字节0006表示字符串的长度为6字节,则后面跟着的6字节3C 69 6E 69 74 3E,通过ASCII码转换后,表示的是字符串“”。

第十六个常量(其实是第十七个),01,表类型CONSTANT_Utf8_info,接下来的2字节0003表示字符串的长度为3字节,则后面跟着的3字节28 29 56,通过ASCII码转换后,表示的是字符串“()V”。

第十七个常量(其实是第十八个),01,表类型CONSTANT_Utf8_info,接下来的2字节0004表示字符串的长度为4字节,则后面跟着的4字节43 6F 64 65,通过ASCII码转换后,表示的是字符串“Code”。

第十八个常量(其实是第十九个),01,表类型CONSTANT_Utf8_info,接下来的2字节000F表示字符串的长度为15字节,则后面跟着的15字节4C 69 6E 65 4E 75 6D 62 65 72 54 61 62 6C 65,通过ASCII码转换后,表示的是字符串“LineNumberTable”。

第十九个常量(其实是第二十个),01,表类型CONSTANT_Utf8_info,接下来的2字节0003表示字符串的长度为3字节,则后面跟着的3字节61 64 64,通过ASCII码转换后,表示的是字符串“add”。

第二十个常量(其实是第二十一个),01,表类型CONSTANT_Utf8_info,接下来的2字节0005表示字符串的长度为5字节,则后面跟着的5字节28 49 49 29 49,通过ASCII码转换后,表示的是字符串“(II)I”。

第二十一个常量(其实是第二十二个),01,表类型CONSTANT_Utf8_info,接下来的2字节000A表示字符串的长度为10字节,则后面跟着的10字节53 6F 75 72 63 65 46 69 6C 65,通过ASCII码转换后,表示的是字符串“SourceFile”。

第二十二个常量(其实是第二十三个),01,表类型CONSTANT_Utf8_info,接下来的2字节0019表示字符串的长度为25字节,则后面跟着的25字节42 79 74 65 43 6F 64 65 54 65 73 74 49 6E 52 6F 79 6F 6C 65 2E 6A 61 76 61,通过ASCII码转换后,表示的是字符串“ByteCodeTestInRoyole.java”。

第二十三个常量(其实是第二十四个),0C,对应的十进制12,根据表类型可知,对应的表为CONSTANT_NameAndType_info,根据结构总表,可知接下来的两个字节0010表示指向该字段或方法名称的索引,紧接着的2个字节0011表示指向该字段或该方法描述符的索引。即:

0010:指向常量池中的第16个常量。

0011:指向常量池中的第17个常量。

第二十四个常量(其实是第二十五个),01,表类型CONSTANT_Utf8_info,接下来的2字节002C表示字符串的长度为44字节,则后面跟着的44字节63 6F 6D 2F 65 78 61 6D 70 6C 65 2F 68 65 6C 6C 6F 72 6F 79 6F 6C 65 2F 42 79 74 65 43 6F 64 65 54 65 73 74 49 6E 52 6F 79 6F 6C 65,通过ASCII码转换后,表示的是字符串“com/example/helloroyole/ByteCojideTestInRoyole”。

第二十五个常量(其实是第二十六个),01,表类型CONSTANT_Utf8_info,接下来的2字节0010表示字符串的长度为16字节,则后面跟着的16字节6A 61 76 61 2F 6C 61 6E 67 2F 4F 62 6A 65 63 74,通过ASCII码转换后,表示的是字符串“java/lang/Object”。

第二十六个常量(其实是第二十七个),01,表类型CONSTANT_Utf8_info,接下来的2字节0014表示字符串的长度为16字节,则后面跟着的20字节6A 61 76 61 2F 69 6F 2F 53 65 72 69 61 6C 69 7A 61 62 6C 65,通过ASCII码转换后,表示的是字符串“java/io/Serializable”。

第二十七个常量(其实是第二十八个),01,表类型CONSTANT_Utf8_info,接下来的2字节0013表示字符串的长度为19字节,则后面跟着的19字节6A 61 76 61 2F 6C 61 6E 67 2F 43 6C 6F 6E 65 61 62 6C 65,通过ASCII码转换后,表示的是字符串“java/lang/Cloneable”。

第二十八个常量(其实是第二十九个),01,表类型CONSTANT_Utf8_info,接下来的2字节001B表示字符串的长度为27字节,则后面跟着的27字节63 6F 6C 6F 72 5F 74 65 6D 70 65 72 61 74 75 72 65 5F 74 69 74 6C 65 5F 6B 65 79,通过ASCII码转换后,表示的是字符串“color_temperature_title_key”。

第二十九个常量(其实到这里常量池已经分析完毕),也就是最后一个,发现紧接着的一个字节是00,查看类型表,what? 没有对应的类型?!难道是哪里出错了?暂时先不管。。。

其实我们上面这样一个个的分析字节码很累的,我们只是为了了解class文件的原理才来一步一步分析每一个二进制字节码,但自己实操过一次后,印象还是很深刻的,其实JDK提供了现成的工具可以直接解析此二进制文件,即javap工具(在JDK的bin目录下),我们通过javap命令来解析此class文件,结果如下图:

java字节码文件 JAVA字节码文件名_java_09


我们看到明明是有29个常量,仔细一看发现第9个常量之后,索引直接变成11了,10消失了。这也就是上文标红字体备注1所提到的坑。现在来填坑,经查找资料表明:long或者double类型的常量在常量池里要占据两个索引。因为我这边demo源码里面实现了Serializable接口,自动生成了一个long类型的serialVersionUID,所以上图第九个常量表示long的常量要占两个索引,下一个变量直接就是从索引11开始。所以上述红色字体分割线后面的常量应该都加1,即第十个常量,应该是第十一个。以此类推。。。最终是29个常量。权威解释,如果英文好的同学可以参考官方文档说明:

https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.6.1

或者这个链接:https://www.zhihu.com/question/22930911,此处不再深入,常量池到此为止。

访问标志

回顾下class文件的结构,魔数-版本号-常量池之后就是访问标志,紧跟在常量池之后的是访问标志,占两个字节,即0021,访问标志代表类或接口的访问信息,比如该class文件是类还是接口,是否被定义成public,是否是abstract,如果是类,是否被声明成final等等。各种访问标志参考下图:

java字节码文件 JAVA字节码文件名_java字节码_10


我们发现这个表里无法查到0021这个值,原因是0021 = 0001+0020,也就是表示当前的class的访问标志是ACC_PUBLIC|ACC_SUPER。ACC_PUBLIC和代码里的public关键字相对应。ACC_SUPER表示当用invokespecial指令来调用父类的方法时需要特殊处理。

类索引

紧接着的2个字节0002表示当前类的索引。即:

0002:表示指向常量池中第2个常量,我们在上面分析常量池的时候已经分析了,第2个常量,又指向第25个常量,该常量类型是CONSTANT_Utf8_info,经过ASCII码解析后字符串为“com/example/helloroyole/ByteCojideTestInRoyole”,表示当前的类名。

父类索引

紧接着的2个字节0003表示父类的索引。即:

0003:表示指向常量池中第3个常量,上面也已经分析了,第3个常量,又指向第26个常量,该常量类型也是CONSTANT_Utf8_info,经过ASCII码解析后字符串为“java/lang/Object”,表示当前父类的类目。我们知道java中所有的类都基础自Object类。

接口索引计数器

紧接着的2个字节0002表示接口索引计数器,表示这个类实现了2个接口,每个接口占2个字节,则接下来的4个字节0004 0005代表了2个接口。即:

0004:表示指向常量池中第4个常量,上面已经分析,第4个常量,又指向了第27个常量,该常量类型也是CONSTANT_Utf8_info,经过ASCII码解析后字符串为“java/io/Serializable”,表示当前实现的接口为Serializable接口。

0005:表示指向常量池中第5个常量,上面已经分析,第5个常量,又指向了第28个常量,该常量类型也是CONSTANT_Utf8_info,经过ASCII码解析后字符串为“java/lang/Cloneable”,表示当前实现的接口为Cloneable接口。

综上所述,可以得出如下结论:当前类ByteCojideTestInRoyole继承自Object类,并实现了Serializable和Cloneable这两个接口。

字段表

java字节码文件 JAVA字节码文件名_java_11


紧跟在后面就是字段表了,字段表的主要功能是用来描述类或者接口中声明的变量。这里的字段包含了类级别变量以及实例变量,但是不包括方法内部声明的局部变量。因为一个类中的变量个数是不固定的,因此在字段表集合之前还是用一个计数器来表示变量的个数,即接下来的2个字节0003。

0003:表示类中声明了3个变量(class文件中叫字段),我这demo里面确实声明了3个变量,分别是long类型的serialVersionUID,String类型的KEY_SUMMARY,以及int型的count。每个成员变量对应个field_info(我不知道官方是不是这样命名的),其结构如下:

field_info{
    u2 access_flags  //字段的访问标志
    u2 name_index    //字段的名称索引(也就是变量名)
    u2 descriptor_index //字段的描述索引(也就是变量的类型)
    u2 attributes_count //属性计数器
    attribute_info
}

即接下来的8个字节00 1A 00 06 00 07 00 01分别表示第一个变量的访问标志,名称索引,描述索引,计数器。即:

001A:表示访问标志

0006:表示变量名,指向常量池中的第6个常量,该常量类型为CONSTANT_Utf8_info,通过ASCII码表转换后,表示的是字符串“serialVersionUID”。即表示变量名为serialVersionUID。

0007:表示变量的类型,指向常量池中的第7个常量,该常量类型为CONSTANT_Utf8_info,通过ASCII码表转换后,表示的是字符串“J”。即表示变量的类型为long。为什么J表示long,可以参考:https://www.jb51.net/article/116313.htm

0001:属性计数器,此处为1,表示有一个属性。

每个属性都是一个attribute_info结构:

attribute_info {
   u2 attribute_name_index;
   u4 attribute_length;
   u1 info[attribute_length];
}

紧接着的2个字节0008表示该属性名字的索引,即:

0008:十进制8,表示指向第8个常量,类型为CONSTANT_Utf8_info,通过ASCII码转换后,表示的是字符串“ConstantValue”。表示这是一个常量

紧接着的4个字节00 00 00 02表示该属性的长度

紧接着的2个字节00 09表示该变量的值,即:

0009:十进制9,表示指向第9个常量,类型为CONSTANT_Long_info,表示长整型:-2793625091785878343L。

紧跟着的8个字节00 1A 00 0B 00 0C 00 01分别表示第二个变量的访问标志,名称索引,描述索引,计数器。即:

001A:表示访问标志

000B:表示变量名,指向常量池中的第11个常量,该常量类型为CONSTANT_Utf8_info,通过ASCII码表转换后,表示的是字符串“KEY_SUMMARY”。即表示变量名为KEY_SUMMARY。

000C:表示变量的类型,指向常量池中的第7个常量,该常量类型为CONSTANT_Utf8_info,通过ASCII码表转换后,表示的是字符串“Ljava/lang/String;”。即表示变量的类型为String。

0001:属性计数器,此处为1,表示有一个属性。

紧接着的2个字节0008表示该属性名字的索引,即:

0008:十进制8,表示指向第8个常量,类型为CONSTANT_Utf8_info,通过ASCII码转换后,表示的是字符串“ConstantValue”。表示这是一个常量

紧接着的4个字节00 00 00 02表示该属性的长度

紧接着的2个字节00 09表示该变量的值,即:

000D:十进制9,表示指向第13个常量,第13个常量又指向第29个常量,类型为CONSTANT_Utf8_info,通过ASCII码转换后,表示的是字符串“color_temperature_title_key”。

紧跟着的8个字节00 02 00 0E 00 0F 00 00分别表示第三个变量的访问标志,名称索引,描述索引,计数器。即:

0002:表示E访问标志

000E:表示变量名,指向常量池中的第14个常量,该常量类型为CONSTANT_Utf8_info,通过ASCII码表转换后,表示的是字符串“count”。即表示变量名为count。

000F:表示变量的类型,指向常量池中的第15个常量,该常量类型为CONSTANT_Utf8_info,通过ASCII码表转换后,表示的是字符串“I”。即表示变量的类型为int。

0000:属性计数器,此处为0,表示没有属性。

方法表

首先看第一个方法,如下截图:

java字节码文件 JAVA字节码文件名_常量池_12


字段表之后跟着的就是方法表,方法表常量也是以一个计数器开始的,前2个字节,即0002,表示我这个demo里面有两个方法,可我明明只定义了一个add方法,哪来的2个方法,这是因为默认构造方法也算一个。方法表的结构如下:

method_info{
    u2 access_flags;
    u2 name_index;
    u2 descriptor_index;
    u2 attributes_count;
    attribute_info attributes;
}

接下来的8个字节00 01 00 10 00 11 00 01分别表示访问标志,方法名,方法返回值,属性计数器。即:

0001:表示访问标志为public

0010:十进制16,表示指向第16个常量,其结构为CONSTANT_Utf8_info,通过ASCII码转换后,表示的是字符串“”。表示构造方法。

0011:十进制17,表示指向第17个常量,其结构为CONSTANT_Utf8_info,通过ASCII码转换后,表示的是字符串“()V”。构造方法是没有返回值的。

0001:十进制1,表示有1个属性。

其中attribute_info结构如下:

attribute_info {
  u2 attribute_name_index;
  u4 attribute_length;
  u1 info[attribute_length];
 }

接下来的2个字节0012,十进制18,表示常量池中第18个常量,类型为CONSTANT_Utf8_info,通过ASCII码转换后,表示的是字符串“Code”。即:

0012:表示该属性是Code。

接下来的4个字节0000001D,表示属性所包含的字节数为29。

code attribute的结构如下:

Code_attribute {
          u2 attribute_name_index;
          u4 attribute_length;
          u2 max_stack;
          u2 max_locals;
          u4 code_length;
          u1 code[code_length];
          u2 exception_table_length;
          {
               u2 start_pc;
               u2 end_pc;
               u2 handler_pc;
               u2 catch_type;
          } exception_table[exception_table_length];
          u2 attributes_count;
          attribute_info attributes[attributes_count];
     }

上面说到code属性包含29个字节,这里不包括attribute_name_index,attribute_length。

max_stack表示这个方法运行的任何时刻所能达到的操作数栈的最大深度,即接下来的2个字节0001。

max_locals表示方法执行期间创建的局部变量的数目,包含用来表示传入的参数的局部变量,这里是0001。

接下来的4个字节表示00 00 00 05表示code_length长度为5,紧接着的5个字节2a b7 00 01 b1为对应的字节码指令的指令码,可参照如下指令码对应的助记符:

2a aload_0
b7 invokespecial
00 nop
01 aconst_null
b1 return

这就是该方法被调用时,虚拟机所执行的字节码。

接下来时exception_table,这里存放的是异常处理信息,不过我这里的demo代码没有做异常处理,所以接下来的2字节0000表示exception_table_length长度为0;接下来的2个字节是该方法的附加属性0001,表示有1个属性,接下来的2个字节0013表示该属性名字的索引,即:

0013:十进制19,指向常量池中第19个常量,类型为CONSTANT_Utf8_info,通过ASCII码转换后,表示的是字符串“LineNumberTable”。表示属性名为LineNumberTable。

这个属性用来表示code数组中的字节码和java代码行数之间的关系。这个属性可以用来在调试的时候定位代码执行的行数。LineNumberTable的结构如下:

LineNumberTable_attribute {
            u2 attribute_name_index;
            u4 attribute_length;
            u2 line_number_table_length;
            { u2 start_pc;
            u2 line_number;
       } line_number_table[line_number_table_length];
  }

接下来的4个字节00 00 00 06表示该属性的长度为6,第一方法即构造函数到这里就差不多结束了。

接下来,再简单看一下第二个方法,如下截图:

java字节码文件 JAVA字节码文件名_常量池_13


该方法就稍微快一点过一遍:首先4个字节0001 0014 0015 0001分别表示访问标志,方法名,返回值,属性计数器。即:

0001:表示public

0014:表示指向第20个常量,类型为CONSTANT_Utf8_info,通过ASCII码转换后,表示的是字符串“add”。即表示方法名

0015:表示指向第21个常量,类型为CONSTANT_Utf8_info,通过ASCII码转换后,表示的是字符串“(II)I”。即表示返回值为int型,并且方法带两个int类型的参数。

0001:表示有一个属性。

接下来的2个字节0012,十进制18,表示常量池中第18个常量,类型为CONSTANT_Utf8_info,通过ASCII码转换后,表示的是字符串“Code”。即:(这里跟方法1构造方法一样)

0012:表示该属性是Code。

接下来的4个字节0000001C,表示属性所包含的字节数为28。

max_stack表示这个方法运行的任何时刻所能达到的操作数栈的最大深度,即接下来的2个字节0002。(对比方法1构造函数次数字节码是0001)

max_locals表示方法执行期间创建的局部变量的数目,包含用来表示传入的参数的局部变量,这里是0003。(对比方法1构造函数次数字节码是0001)

接下来的4个字节表示00 00 00 04表示code_length长度为4,紧接着的5个字节1B 1C 60 AC为对应的字节码指令的指令码,可参照如下指令码对应的助记符:

(此处的字节码 1B 1C 60 AC对应的助记符我没有找到,我的理解是官方应该会有一个类似的表列出所有的对应关系,如有错误请指出,谢谢)

此方法一样没有做异常处理,所以接下来2个字节一样是00 00,接下来的2个字节00 01,表示附加属性有1个。接下来的2个字节0013表示该属性名字的索引(同方法1构造方法),即:

0013:十进制19,指向常量池中第19个常量,类型为CONSTANT_Utf8_info,通过ASCII码转换后,表示的是字符串“LineNumberTable”。表示属性名为LineNumberTable。

属性表

最后就是属性表了,这里的属性表示整个class的附加属性。同样的,紧接着上面的方法表之后的2个字节0001表示有1个属性,属性表并没有一个固定的结构,各种不同的属性只要满足以下结构即可:

attribute_info{
 u2 attribute_name_index;
 u4 attribute_lenght;
 u2 sourcefile_index;
}

接下来的2个字节表示0016表示属性名字的索引,即:

0016:十进制22,表示常量池的第22个常量,表类型CONSTANT_Utf8_info,通过ASCII码转换后,表示的是字符串“SourceFile”。

接下来的4个字节00 00 00 02表示长度为2,最后2个字节0017,指向常量池里的第23个常量,为ByteCodeTestInRoyole.java,这个属性表明当前的class文件是从ByteCodeTestInRoyole.java文件编译而来。