Java字节码解读

Java最重要的一个机制就是虚拟机JVM,通过执行编译好的字节码.class文件,可以实现跨平台的可移植性,那么小伙伴们会不会好奇字节码文件是怎样的,它是怎么工作的?参考来源,为了便于回顾,特此按自己思路重头来一遍,接下来我们就一起来解读一下字节码文件,前方高能,大脑请提前清空。

1. 前期准备

1.1 首先编写一个测试程序,简单地在main方法中声明一个Integer包装类型变量i和一个基本类型int类型变量n,代码如下。

public class EnTest {
    public static void main(String[] args) {
        Integer i = 10;
        int n = i;
    }
}

1.2 命令行javac编译得到.class字节码文件

1.3 安装二进制文件编辑器,博主使用Notepad++,可参照我的另一篇博客,你也可以使用其它编辑器。

2. 打开字节码文件

2.1 使用安装HexEditor插件的Notepad++打开字节码文件,需要说明的是,文件中都是16进制,所以两位刚好是一个字节,如下图。

2.2 按照对照表分析字节码文件

2.2.1 从文件取4个字节【ca fe ba be】得到第一块内容,文件类型

最先的4个字节ca fe ba be代表了字节码文件的类型,只要是class文件,开头的四个字节就是cafebabe,音译咖啡宝贝也就是java的logo热腾腾的咖啡。

2.2.2接着从文件取2+2个字节【00 00 00 3b】 得到第二块内容,java版本

其中开始的【00 00】这两个字节的十进制是0,代表,次版本号是0,;然后【00 3b】的十进制是59,代表主版本号是15,所以我的java版本是java 15

2.2.3接着取2+n个字节得到第三块内容——常量池

先取两个字节00 19,化为十进制为25,表示有25项常量,但是第一项常量是系统预留,所以是24项常量,下图是常量对应表,每一个常量由两三项数据构成,但是每一项常量的第一个数据都是一个字节大小,所以接下来我们取一个字节

2.2.3.1 第一项常量,取一个字节【0a】

0a十进制是10,根据值是10确定常量类型是CONSTANT_Methodref_info,它的第二项数据占两个字节,是索引,表示指向第几项常量。取接下来的两个字节【00 02】十进制是2,表示指向第2项常量,现在还没求第二项常量,看后面。然后第三项数据也是占两个字节,也是索引,再取接下来的【00 03】十进制是3,表示指向第3项常量,现在还没求第三项常量,第一项常量结束。

2.2.3.2第二项常量,取一个字节【07】

07十进制是7,根据7确定常量类型是CONSTANT_class_info,它的第二项数据占两个字节,是索引,也表示指向第几项常量,取接下来的两个字节【00 04】十进制是4,指向第4项常量,第2项常量结束。

2.2.3.3第三项常量,取一个字节【0c】

0c十进制是12,根据值是12确定常量类型是CONSTANT_NameAndType_info,它的第二项数据占两个字节,是索引,表示指向第几项常量。取接下来的两个字节【00 05】十进制是5,表示指向第5项常量。然后第三项数据也是占两个字节,也是索引,再取接下来的【00 06】十进制是6,表示指向第6项常量,第3项常量结束。

2.2.3.4第四项常量,取一个字节【01】

01十进制是1,根据值是1确定常量类型是CONSTANT_Utf8_info,它的第二项数据占两个字节,是长度length。取接下来的两个字节【00 10】十进制是16,表示长度是16。然后第三项数据占一个字节,是长度为length=16的字符串,取接下来的【6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63 74】转化为十进制,然后根据ASCII码转化为一个个字符,字符串使用程序计算如下图,得到“java/lang/Object”,第4项常量结束。

2.2.3.5第5项常量,取一个字节【01】 

01十进制是1,根据值是1确定常量类型是CONSTANT_Utf8_info,它的第二项数据占两个字节,是长度length。取接下来的两个字节【00 06】十进制是6,表示长度是6。然后第三项数据占一个字节,是长度为length=6的字符串,取接下来的【3c 69 6e  69 74 3e 】转化为十进制,然后根据ASCII码转化为一个个字符,字符串使用程序计算如下图,得到“<init>”,第5项常量结束。

2.2.3.6第6项常量,取一个字节【01】 

01十进制是1,根据值是1确定常量类型是CONSTANT_Utf8_info,它的第二项数据占两个字节,是长度length。取接下来的两个字节【00 03】十进制是3,表示长度是3。然后第三项数据占一个字节,是长度为length=3的字符串,取接下来的【28 29 56】转化为十进制,然后根据ASCII码转化为一个个字符,字符串使用程序计算如下图,得到“()V”,第6项常量结束。

2.2.3.7 第7项常量,取一个字节【0a】

0a十进制是10,根据值是10确定常量类型是CONSTANT_Methodref_info,它的第二项数据占两个字节,是索引,表示指向第几项常量。取接下来的两个字节【00 08】十进制是8,表示指向第8项常量,现在还没求第8项常量,看后面。然后第三项数据也是占两个字节,也是索引,再取接下来的【00 09】十进制是9,表示指向第9项常量,现在还没求第9项常量,第7项常量结束。

……………………………………………………………………

……………………………………………………………………

以此类推可以得到其它17项常量,我就不一一推导了,使用javap -v EnTest命令得到反编译内容得到常量池内容和常量池解读结束位置如下图。

2.2.4根据对照表第四块内容访问标志位,取两个字节【00 21】

【00 21】表示0x0020和0x0001进行或操作得到,访问属性表如下图所示,即表示类属性是public和继承了父类(默认继承Object类)。

2.2.5根据对照表得到第5块内容本类名称,取两个字节【00 11】

【00 11】十进制是17,表示此处从第17项常量获得值,而且第17项常量指向第18项常量,第18项常量值为“EnTest”,代表本类名称为“EnTest”。

2.2.6根据对照表得到第6块内容父类名称,取两个字节【00 11】

【00 02】十进制是2,此处从第2项常量获得值,而且第2项常量指向第4项常量,第4项常量值为“java/lang/Object”,代表父类名称为“java/lang/Object”。

2.2.7根据对照表得到第7块内容接口信息,先取两个字节【00 00】

【00 00】表示接口数量为0,第7块内容结束。

2.2.8根据对照表得到第8块内容类级别变量信息,先取两个字节【00 00】

该处描述类和接口的变量,不包括方法中的局部变量,我们只定义了两个变量,所以此处为0,如果有类级别变量,可以安装下图字段表进行解析。

2.2.9根据对照表得到第9块内方法信息,先取两个字节【00 02】

【00 02】十进制是2,表示有两个方法,但我们只有一个main方法,我们接下来根据方法表来推断一下另一个方法。

先取两个字节【00 01】,该处是访问标志符,根据访问属性表可知0x0001表示public,其次取两个字节【00 05】十进制是5,表示从常量池取第5项常量值得到方法名“<init>”,然后取两个字节【00 06】十进制是6,表示从常量池取第6项常量值得到方法描述"()V",接着取两个字节【00 01】十进制是1,表示有一个属性,需要根据属性表进行解析,属性表如下图。

先取两个字节表示属性名字在常量池的索引,取两个字节【00 13】十进制是19,第19项的值是“Code”,所以属性名称是“Code”,既然是Code属性,虚拟机就会需要调用Code属性表,不再使用上述属性表结构解读。

由于Code属性表第二项的属性长度要4个字节,则取4个字节【00 00 00 1d】十进制是29,长度 是29,所以往后29个字节都是该Code属性的内容。接下来取两个字节【00 01】表示max_stack=1,再取两个字节【00 01】表示max_locals=1,再取4个字节【00 00 00 05】表示字节码指令长度是5,接下来5个字节【2a b7 20 01 b1  】具体意思就不展开了,由于后续解析内容涉及更多属性表内容,但是平时又不大涉及,故不多多讲解,到此结束解读。

最终使用javap -v EnTest反编译的部分字节码内容如下。

猜你喜欢

转载自blog.csdn.net/yldmkx/article/details/108942906