Java虚拟机之class文件结构

Class文件是一组按以8位字节为基础单元的二进制流,各个数据项目严格按照顺序紧凑的排列在Class文件中,中间没有添加任何的分隔符,整个Class文件中储存的内容几乎全部是程序运行的必要数据,没有空隙存在。

当遇到8位字节以上的空间的数据项时,则会按照高位在前的方式分割成若干个8位字节进行存储。

Class文件中有两种数据类型,分别是无符号数和表。

 

                                                                            Class文件结构

魔数

Class文件版本

常量池

访问标志

类索引,父类索引,接口索引集合

字段表集合

方法表集合

属性表集合

 


本文参照了一篇csdn博文《jdk源码解析(六)——类(class)文件结构》                    如有侵权请联系删除

原文地址:https://blog.csdn.net/sinat_38259539/article/details/78248454

原文内容:

每一个 Class 文件对应于一个如下所示的 ClassFile 结构体。

ClassFile 结构体中,各项的含义描述如下:

1,无符号数,以u1、u2、u4、u8分别代表1个字节、2个字节、4个字节、8个字节的无符号数

2,表是由多个无符号数或者其它表作为数据项构成的复合数据类型,所以表都以“_info”结尾,由多个无符号数或其它表构成的复合数据类型

每个部分出现的次数和数量见下表:


magic
     魔数,魔数的唯一作用是确定这个文件是否为一个能被虚拟机所接受的 Class 文件。魔数值固定为 0xCAFEBABE,不会改变。


 


minor_version、major_version
     副版本号和主版本号,minor_version 和 major_version 的值分别表示 Class 文件的副、主版本。它们共同构成了 Class 文件的格式版本号。譬如某个 Class 文件的主版本号为 M,副版本号为 m,那么这个 Class 文件的格式版本号就确定为 M.m。Class 文件格式版本号大小的顺序为:1.5 < 2.0 < 2.1。
一个 Java 虚拟机实例只能支持特定范围内的主版本号(Mi 至 Mj)和 0 至特定范围内(0 至 m)的副版本号。假设一个 Class 文件的格式版本号为 V,仅当 Mi.0 ≤ v ≤ Mj.m
成立时,这个 Class 文件才可以被此 Java 虚拟机支持。不同版本的 Java 虚拟机实现支持的版本号也不同,高版本号的 Java 虚拟机实现可以支持低版本号的 Class 文件,反之则不成立 。

注意:Oracle 的 JDK 在 1.0.2 版本时,支持的 Class 格式版本号范围是 45.0 至 45.3;JDK 版本在 1.1.x时,支持的 Class 格式版本号范围扩展至 45.0 至 45.65535;JDK 版本为 1. k 时(k ≥2)时,对应的 Class文件格式版本号的范围是 45.0 至 44+k.0

下表列举了Class文件版本号



constant_pool_count
常量池计数器,constant_pool_count的值等于constant_pool表中的成员数加1。constant_pool 表的索引值只有在大于 0 且小于 constant_pool_count 时才会被认为是有效的 ,对于 long 和 double 类型有例外情况,后续在讲解。

注意:虽然值为 0 的 constant_pool 索引是无效的,但其他用到常量池的数据结构可以使用索引 0 来表示“不引用任何一个常量池项”的意思。



constant_pool[ ]
常量池,constant_pool 是一种表结构(这里需要列举一下表就会明白,这个在下面的例子中会有讲解这个结构,返回来在读就会明白),它包含 Class 文件结构及其子结构中引用的所有字符串常量、类或接口名、字段名和其它常量。常量池中的每一项都具备相同的格式特征——第一个字节作为类型标记用于识别该项是哪种类型的常量,称为“tagbyte”。常量池的索引范围是 1 至 constant_pool_count−1。

1常量池的项目类型

2每一种类型的格式特征:这里用CONSTANT_Class_info举个例子:

常量池主要存放两大类常量:字面量(literal)和符号引用。字面量比较接近java语言层面的常量概念,比如文本字符串、声明的final的常量值等。符号引用属于编译原理方面概念。包括下面三类常量:类和接口的全局限定名。字段的名称和描述符。方法的名称和描述符。这三类稍后在详细讲解。



 access_flags
访问标志,access_flags 是一种掩码标志,用于表示某个类或者接口的访问权限及基础属性。access_flags 的取值范围和相应含义见表 4.1 所示。

 带有 ACC_SYNTHETIC 标志的类,意味着它是由编译器自己产生的而不是由程序员编写的源代码生成的。

 带有 ACC_ENUM 标志的类,意味着它或它的父类被声明为枚举类型。

 带有 ACC_INTERFACE 标志的类,意味着它是接口而不是类,反之是类而不是接口。如果一个 Class 文件被设置了 ACC_INTERFACE 标志,那么同时也得设置ACC_ABSTRACT 标志(JLS §9.1.1.1)。同时它不能再设置 ACC_FINAL、ACC_SUPER 和 ACC_ENUM 标志。

 注解类型必定带有 ACC_ANNOTATION 标记,如果设置了 ANNOTATION 标记,ACC_INTERFACE 也必须被同时设置。如果没有同时设置 ACC_INTERFACE 标记,那么这个Class文件可以具有表4.1中的除ACC_ANNOTATION外的所有其它标记。当然 ACC_FINAL 和 ACC_ABSTRACT 这类互斥的标记除外(JLS §8.1.1.2)。

 ACC_SUPER 标志用于确定该 Class 文件里面的 invokespecial 指令使用的是哪一种执行语义。目前 Java 虚拟机的编译器都应当设置这个标志。ACC_SUPER 标记是为了向后兼容旧编译器编译的 Class 文件而存在的,在 JDK1.0.2 版本以前的编译器产生的 Class 文件中,access_flag 里面没有 ACC_SUPER 标志。同时,JDK1.0.2 前的 Java 虚拟机遇到 ACC_SUPER 标记会自动忽略它。

 在表 4.1 中没有使用的 access_flags 标志位是为未来扩充而预留的,这些预留的标志为在编译器中会被设置为 0, Java 虚拟机实现也会自动忽略它们。



 this_class

类索引,this_class 的值必须是对 constant_pool 表中项目的一个有效索引值。constant_pool 表在这个索引处的项必须为 CONSTANT_Class_info 类型常量,表示这个 Class 文件所定义的类或接口。


 

super_class

父类索引,对于类来说,super_class 的值必须为 0 或者是对 constant_pool 表中项目的一个有效索引值。如果它的值不为 0,那 constant_pool 表在这个索引处的项

必须为 CONSTANT_Class_info 类型常量(§4.4.1),表示这个 Class 文件所定义的类的直接父类。当前类的直接父类,以及它所有间接父类的 access_flag 中都不能带有 ACC_FINAL 标记。对于接口来说,它的 Class 文件的 super_class 项的值必须是对 constant_pool 表中项目的一个有效索引值。constant_pool 表在这个索引处的
项必须为代表 java.lang.Object 的 CONSTANT_Class_info 类型常量(§4.4.1)。如果 Class 文件的 super_class 的值为 0,那这个 Class 文件只可能是定义的是
java.lang.Object 类,只有它是唯一没有父类的类。



interfaces_count
接口计数器,interfaces_count 的值表示当前类或接口的直接父接口数量。



 interfaces[]
接口表,interfaces[]数组中的每个成员的值必须是一个对 constant_pool 表中项目的一个有效索引值,它的长度为 interfaces_count。每个成员 interfaces[i] 必须为 CONSTANT_Class_info 类型常量(§4.4.1),其中 0 ≤ i <interfaces_count。在 interfaces[]数组中,成员所表示的接口顺序和对应的源代码中给定的接口顺序(从左至右)一样,即 interfaces[0]对应的是源代码中最左边的接口。



fields_count
字段计数器,fields_count 的值表示当前 Class 文件 fields[]数组的成员个数。fields[]数组中每一项都是一个 field_info 结构(§4.5)的数据项,它用于表示该类或接口声明的类字段或者实例字段 。

注意::类字段即被声明为 static 的字段,也称为类变量或者类属性,同样,实例字段是指未被声明为static 的字段。由于《Java 虚拟机规范》中,“Variable”和“Attribute”出现频率很高且在大多数场景中具备其他含义,所以译文中统一把“Field”翻译为“字段”,即“类字段”、“实例字段”。



fields[]
字段表,fields[]数组中的每个成员都必须是一个 fields_info 结构(§4.5)的数据项,用于表示当前类或接口中某个字段的完整描述。fields[]数组描述当前类或接口
声明的所有字段,但不包括从父类或父接口继承的部分。



 methods_count
方法计数器,methods_count 的值表示当前 Class 文件 methods[]数组的成员个数。Methods[]数组中每一项都是一个 method_info 结构(§4.5)的数据项。



methods[]
方法表,methods[]数组中的每个成员都必须是一个 method_info 结构(§4.6)的数据项,用于表示当前类或接口中某个方法的完整描述。如果某个 method_info 结构
的 access_flags 项既没有设置 ACC_NATIVE 标志也没有设置 ACC_ABSTRACT 标志,那么它所对应的方法体就应当可以被 Java 虚拟机直接从当前类加载,而不需要引用其它类。method_info 结构可以表示类和接口中定义的所有方法,包括实例方法、类方法、实例初始化方法方法(§2.9)和类或接口初始化方法方法(§2.9)。methods[]数组
只描述当前类或接口中声明的方法,不包括从父类或父接口继承的方法。



attributes_count
属性计数器,attributes_count 的值表示当前 Class 文件 attributes 表的成员个数。attributes 表中每一项都是一个 attribute_info 结构(§4.7)的数据项。



attributes[]
属性表,attributes 表的每个项的值必须是 attribute_info 结构(§4.7)。在本规范里,Class 文件结构中的 attributes 表的项包括下列定义的属性:
InnerClasses(§4.7.6)、EnclosingMethod(§4.7.7)、Synthetic(§4.7.8)、Signature(§4.7.9)、SourceFile(§4.7.10),SourceDebugExtension(§4.7.11)、Deprecated(§4.7.15)、RuntimeVisibleAnnotations(§4.7.16)、RuntimeInvisibleAnnotations(§4.7.17)以及BootstrapMethods(§4.7.21)属性。对于支持 Class 文件格式版本号为 49.0 或更高的 Java 虚拟机实现,必须正确识别并读取 attributes 表中的 Signature(§4.7.9)、RuntimeVisibleAnnotations(§4.7.16)和RuntimeInvisibleAnnotations(§4.7.17)属性。对于支持 Class 文件格式版本号为 51.0 或更高的 Java 虚拟机实现,必须正确识别并读取 attributes 表中的
BootstrapMethods(§4.7.21)属性。本规范要求任一 Java 虚拟机实现可以自动忽略 Class 文件的 attributes 表中的若干(甚至全部)它不可识别的属性项。任何本规范未定义的属性不能影响 Class 文件的语义,只能提供附加的描述信息(§4.7.1)。

这16个部分需要在实际应用中仔细研究一下。

列如下面代码:

 

 
  1. package com.clazz;

  2.  
  3. public class TestClass {

  4.  
  5. private int m;

  6. public int inc(){

  7. return m+1;

  8. }

  9. }

经过编译之后class文件用WinHex编辑器打开你会发现

解读:

 1>前4个字节16进制表示0xCAFEBABE 固定不变的魔数。

2>看到第5字节和6字节表示副版本号0x0000和主版本号0x0033也就是十进制51。查找class版本号可知这个class文件可以被JDK1.7.0 或者以上的虚拟机执行的class文件。

3>常量池计数器是从1开始计数,即上图中的偏移地址(0x00000008)即数字16那,换做十进制为22,也就是常量池中有21个常量。索引值范围1~21,

这里注意:将索引值设置为0时有特殊含义,不引用任何一个常量池项目的含义。Class文件中只有常量池的容量是从1计数开始。其它一般从0开始

4>常量池:常量池第一项,(偏移地址0x0000000A)是0x07,查看表6-3的标志发现它属于CONSTANT_Class_info类型。此类型的结构如上面的常量池的6-4图,其中tag是标志位已经说过了用于区分常量类型,name_index是一个索引值,它指向常量池中一个CONSTANT_Utf8_info类型的常量,这里name_index的值(偏移地址0x0000000B)为0x0002也指向了常量池的第二项。然后依次继续查找。。。

分析了TestClass.class常量池中的两个,其余19个常量也可以继续计算出来。这里我们可以借助jdk的计算器帮我们完成。在JDK的bin目录中有一个专门分析class文件的字节码工具:javap,使用命令 javap -verbose TestClass  参数输出文件字节码内容。如下:

 

 
  1. F:\Java\jdk\jdk1.7.0_60\bin>javap -verbose TestClass.class

  2. Classfile /F:/Java/jdk/jdk1.7.0_60/bin/TestClass.class

  3. Last modified 2017-10-16; size 373 bytes

  4. MD5 checksum 7d19b6fe8101f913758048f3529eaee4

  5. Compiled from "TestClass.java"

  6. public class com.clazz.TestClass

  7. SourceFile: "TestClass.java"

  8. minor version: 0

  9. major version: 51

  10. flags: ACC_PUBLIC, ACC_SUPER

  11. Constant pool:

  12. #1 = Class #2 // com/clazz/TestClass

  13. #2 = Utf8 com/clazz/TestClass

  14. #3 = Class #4 // java/lang/Object

  15. #4 = Utf8 java/lang/Object

  16. #5 = Utf8 m

  17. #6 = Utf8 I

  18. #7 = Utf8 <init>

  19. #8 = Utf8 ()V

  20. #9 = Utf8 Code

  21. #10 = Methodref #3.#11 // java/lang/Object."<init>":()V

  22. #11 = NameAndType #7:#8 // "<init>":()V

  23. #12 = Utf8 LineNumberTable

  24. #13 = Utf8 LocalVariableTable

  25. #14 = Utf8 this

  26. #15 = Utf8 Lcom/clazz/TestClass;

  27. #16 = Utf8 inc

  28. #17 = Utf8 ()I

  29. #18 = Fieldref #1.#19 // com/clazz/TestClass.m:I

  30. #19 = NameAndType #5:#6 // m:I

  31. #20 = Utf8 SourceFile

  32. #21 = Utf8 TestClass.java

  33. {

  34. public com.clazz.TestClass();

  35. flags: ACC_PUBLIC

  36. Code:

  37. stack=1, locals=1, args_size=1

  38. 0: aload_0

  39. 1: invokespecial #10 // Method java/lang/Object."<init>

  40. ":()V

  41. 4: return

  42. LineNumberTable:省略一部分。。。

这里有许多还是不太懂。需要后期有时间再次进行解读。

5>常量池结束后,紧接着的两个字节代表访问标识(access_flags)偏移量(0x000000DC)为0x0021=0x0001 

6>因为常量池部分没有完全解读出来,所以以后的部分也不是特别懂,总之大概思路就是这样去解读。这里就不做详细解读了。

后期若有机会,就将再次详解本部分。


3、总结:

本部分就讲解到这里,后期如果有机会就继续讲解。字节码指令集也暂时跳过。有机会在详细讲解吧,这些本人觉得了解一部分后就足够用了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值