嚼一嚼 class 文件结构

嚼一嚼 class 文件结构

0、准备

  • win10操作系统
  • 安装 jdk1.8.0_161 并配置好环境变量
  • 安装 Notepad++ 和里面的 HEX-Editor 插件

1、生成一个 class 文件

我们平时写的java代码存储在 .java 文件中,称之为 java 源文件。下面是 Test.java 文件的内容:

我们在 Test.java 文件的同目录下,按住 Shift+鼠标右键,选择”在此处打开**窗口“,然后输入。这个时候,会发现当前目录中生成了一个 Test.class 文件。

这个 class 文件就是我们今天的主角,它是由 java 编译器(javac)翻译 java 源文件(Test.java)得到的字节码文件(Test.class),这种字节码就是 JVM 的“机器语言”。

2、查看 class 文件内容

使用 Notepad++ 打开 Test.class 文件,然后依次选择 插件–>HEX-Editor–>View in HEX。

所以此时看到的内容,是class 文件的十六进制形式。十六进制数字两两相连,这是为什么呢都知道,1byte = 8bit,而 4bit 刚好可以用一个十六进制数字表示,如 1111 = 0xf。所以一个字节刚好可以用两个十六进制数字表示。

下面贴上 class 文件内容:

表的类型有很多,在这里以 java 类形式表示一下第一个表类型:

现在,已经基本了解了常量池中常量项的结构,接下来我们结合上面的表格来翻译常量。

5.3、常量集合

常量池入口指定了常量池的大小为 18,接下来我们一个个读取这些常量。

  • [0a,00,04,00,0f]:0x0a = 10 表示类型是 Methodref,接下来 0x0004 = 4、0x000f=15,表示指向的是第 4 个和第 15 个常量。我猜这是为了缩减 class 文件的大小,故将相同的常量以地址指向的形式来引用。
  • [09,00,03,00,10]:0x09 = 0 表示类型是 Fieldref,后面 0x0003 和 0x0010 表示分别指向第 3 个和第 16 个常量。
  • []07,00,11]:0x07 = 7 表示类型是 Class,引用指向第 17 个常量。
  • [07,00,12]:与上面相同,指向第 18 个常量。
  • [01,00,01,69]:0x01 = 1 表示类型是 Utf8,字符串的长度为 0x0001 = 1,故只需要读取后面一个字节 69。十六进制数字 0x69 对应的字符为小写字母 i,这就是我们前面定义的变量名。

翻译就到这里了,跟着前面的结构对照表,相信你已经可以轻松翻译剩下的常量。

5.4、验证翻译结果

只写代码不做测试是耍流氓,同样的,如果我只告诉各位怎么翻译而不告诉你们怎么验证,那也是在耍流氓。现在我们就通过 JDK 提供的工具,来验证一下,常量到底是不是我前面所说的那样。

在 Test.class 文件的目录中打开命令行,运行 命令,就可以看到常量池的内容。这里贴一下运行结果:

0x21 的二进制为 100001 ,表示 ACC_SUPER 和 ACC_PUBLIC 为 1。这与代码情况也是一致的,我们是使用 JDK1.8 编译的,并且 Test 类确为 public 类型。

7、类索引、父类索引和接口索引集合

7.1、类索引[00,03]

指向第 3 个常量,而第 3 个常量引用第 17 个,值为 Test。表示当前类为 Test。

7.2、父类索引[00,04]

与上面类似,值为 java/lang/Object,表示 Test 父类为 Object。

7.3、接口索引集合

类索引和父类索引之后,紧接着的两个字节存储的是一个 u2 类型的数据,表示接口计数器。这里是[00,00],长度为 0 表示没有实现接口。

8、字段表集合

8.1、字段数量[00,01]

接口索引集合之后,紧接着就是字段表集合,头两个字节存储的便是这个集合的大小,这里可以看到集合大小为 1,表示只有一个字段。

相信你也发现了一些规律,比如集合类型的表,前面就会用一个 u2 类型的数据存储大小。

8.2、字段表[00,02,00,05,00,06]

字段表结构可以分为三部分,分别是 access_flags(u2,修饰符)、name_index(u2,名称)、descriptor_index(u2,类型)。

access_flags 各个标志位含义如下:

方法1:access_flags(u2,访问标志)存储值为 [00,01],name_index(u2,名称)存储值为 [00,07],descriptor_index(u2,类型)存储值为 [00,08],最终得到的结果是 。

表示这是一个构造方法,()V 表示返回值类型是 void。这与我们平常写 java 代码时的语法有点区别,java 代码中构造方法是没有返回值的,想来是因为构造方法都没有返回值,所以让编译器在编译时处理进而方便开发人员,也略微减小了 java 源文件的大小。

9.3、属性表集合

属性表计数器 [00,01] 表示这里有 1 个属性,属性名称 [00,09] 指向第 9 个常量 Code,说明此属性是方法的字节码描述。

对于每个属性,它的名称需要从常量池中引用一个 Utf8 类型,如这里的 [00,09] 引用的 Code。而属性值的结构是完全自定义的,只需要通过一个 u4 的长度属性来说明属性值所占用的位数即可。

这里 [00,00,00,1d] 表示,后面 29 个字节都是属性值。

9.4、另一个方法

前面 9.2 和 9.3 加在一起,翻译完了默认的构造方法。下面快速翻译一下另一个方法。

方法2:access_flags[00,01] –> public,name_index[00,0b] –> 常量11 –> add,descriptor[00,0c] –> 常量12 –> ()I。最终得到的结果是。

属性表计数器 [00,01],属性名称 [00,09] –> Code,属性值长度 [00,00,00,1f] –> 31。

10、附加属性集合

方发表结合之后,就剩最后一部分附加属性集合了。

附加属性计数器 [00,01] 表示这里只有一个附加属性。接下来是一个 u2 类型的数据 [00,0d] –> 常量13 –> SourceFile,SourceFile 属性的结构为 u2(attribute_name_index) + u4(attribute_length) + u2(sourcefile_index),第一个 u2 已经翻译了是 SourceFile,第二个 u4[00,00,00,02] 表示长度是 2,第三个 u2[00,0e] –> 常量14 –> Test.java。

11、总结

最后贴一张图,总结以上的翻译过程:

嚼一嚼 class 文件结构

文章知识点与官方知识档案匹配,可进一步学习相关知识Java技能树首页概览93596 人正在系统学习中

声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

上一篇 2020年7月13日
下一篇 2020年7月13日

相关推荐