以 Java 类示例 中的Rectangle 类为示例
先用 javac 命令编译为 class文件,再使用 javap -v xxx.class 查看结构

  1. Classfile /Users/yuanzeng/IdeaProject/untitled/src/Rectangle.class
  2. Last modified 2020-12-20; size 501 bytes
  3. MD5 checksum 182ab4527e63a234a830ebc7c1f29d36
  4. Compiled from "Rectangle.java"
  5. public class Rectangle implements Shape
  6. minor version: 0
  7. major version: 52
  8. flags: ACC_PUBLIC, ACC_SUPER
  9. Constant pool:
  10. #1 = Methodref #6.#24 // java/lang/Object."<init>":()V
  11. #2 = Fieldref #4.#25 // Rectangle.width:I
  12. #3 = Fieldref #4.#26 // Rectangle.length:I
  13. #4 = Class #27 // Rectangle
  14. #5 = String #28 // 矩形
  15. #6 = Class #29 // java/lang/Object
  16. #7 = Class #30 // Shape
  17. #8 = Utf8 TAG
  18. #9 = Utf8 Ljava/lang/String;
  19. #10 = Utf8 ConstantValue
  20. #11 = Utf8 width
  21. #12 = Utf8 I
  22. #13 = Utf8 length
  23. #14 = Utf8 <init>
  24. #15 = Utf8 (II)V
  25. #16 = Utf8 Code
  26. #17 = Utf8 LineNumberTable
  27. #18 = Utf8 getArea
  28. #19 = Utf8 ()I
  29. #20 = Utf8 getName
  30. #21 = Utf8 ()Ljava/lang/String;
  31. #22 = Utf8 SourceFile
  32. #23 = Utf8 Rectangle.java
  33. #24 = NameAndType #14:#31 // "<init>":()V
  34. #25 = NameAndType #11:#12 // width:I
  35. #26 = NameAndType #13:#12 // length:I
  36. #27 = Utf8 Rectangle
  37. #28 = Utf8 矩形
  38. #29 = Utf8 java/lang/Object
  39. #30 = Utf8 Shape
  40. #31 = Utf8 ()V
  41. {
  42. public Rectangle(int, int);
  43. descriptor: (II)V
  44. flags: ACC_PUBLIC
  45. Code:
  46. stack=2, locals=3, args_size=3
  47. 0: aload_0
  48. 1: invokespecial #1 // Method java/lang/Object."<init>":()V
  49. 4: aload_0
  50. 5: iload_1
  51. 6: putfield #2 // Field width:I
  52. 9: aload_0
  53. 10: iload_2
  54. 11: putfield #3 // Field length:I
  55. 14: return
  56. LineNumberTable:
  57. line 12: 0
  58. line 13: 4
  59. line 14: 9
  60. line 15: 14
  61. public int getArea();
  62. descriptor: ()I
  63. flags: ACC_PUBLIC
  64. Code:
  65. stack=2, locals=1, args_size=1
  66. 0: aload_0
  67. 1: getfield #2 // Field width:I
  68. 4: aload_0
  69. 5: getfield #3 // Field length:I
  70. 8: imul
  71. 9: ireturn
  72. LineNumberTable:
  73. line 19: 0
  74. public java.lang.String getName();
  75. descriptor: ()Ljava/lang/String;
  76. flags: ACC_PUBLIC
  77. Code:
  78. stack=1, locals=1, args_size=1
  79. 0: ldc #5 // String 矩形
  80. 2: areturn
  81. LineNumberTable:
  82. line 24: 0
  83. }
  84. SourceFile: "Rectangle.java"

16进制数据如下

CAFE BABE 0000 0034 0020 0A00 0600 1809 0004 0019 0900 0400 1A07 001B 0800 1C07 001D 0700 1E01 0003 5441 4701 0012 4C6A 6176 612F 6C61 6E67 2F53 7472 696E 673B 0100 0D43 6F6E 7374 616E 7456 616C 7565 0100 0577 6964 7468 0100 0149 0100 066C 656E 6774 6801 0006 3C69 6E69 743E 0100 0528 4949 2956 0100 0443 6F64 6501 000F 4C69 6E65 4E75 6D62 6572 5461 626C 6501 0007 6765 7441 7265 6101 0003 2829 4901 0007 6765 744E 616D 6501 0014 2829 4C6A 6176 612F 6C61 6E67 2F53 7472 696E 673B 0100 0A53 6F75 7263 6546 696C 6501 000E 5265 6374 616E 676C 652E 6A61 7661 0C00 0E00 1F0C 000B 000C 0C00 0D00 0C01 0009 5265 6374 616E 676C 6501 0006 E79F A9E5 BDA2 0100 106A 6176 612F 6C61 6E67 2F4F 626A 6563 7401 0005 5368 6170 6501 0003 2829 5600 2100 0400 0600 0100 0700 0300 1A00 0800 0900 0100 0A00 0000 0200 0500 0200 0B00 0C00 0000 0200 0D00 0C00 0000 0300 0100 0E00 0F00 0100 1000 0000 3300 0200 0300 0000 0F2A B700 012A 1BB5 0002 2A1C B500 03B1 0000 0001 0011 0000 0012 0004 0000 000C 0004 000D 0009 000E 000E 000F 0001 0012 0013 0001 0010 0000 0022 0002 0001 0000 000A 2AB4 0002 2AB4 0003 68AC 0000 0001 0011 0000 0006 0001 0000 0013 0001 0014 0015 0001 0010 0000 001B 0001 0001 0000 0003 1205 B000 0000 0100 1100 0000 0600 0100 0000 1800 0100 1600 0000 0200 17

Class 文件基本结构概述

  • Class文件是一组以8位字节为基础单位的二进制流,当遇到需要8位字节以上空间的数据项时,则会按照高位在前的方式分隔成若干个8位字节进行存储。
  • Class文件由无符号数和表构成。
  • 无符号数:以u1、u2、u4、u8分别代表1个字节、2个字节、4个字节和8个字节的无符号数,可以用来描述数字、索引引用、数量值、按照UTF-8编码构成的字符串值。
  • 表:由多个无符号数或其他表作为数据项构成的复杂数据类型,所有表都习惯性地以“_info”结尾。

Class 文件格式如下:

类型 描述 备注
u4 magic 魔数:0xCAFEBABE
u2 minor_version 小版本号
u2 major_version 主版本号
u2 constant_pool_count 常量池大小,从1开始
cp_info constant_pool[constant_pool_count - 1] 常量池信息
u2 access_flags 访问标志
u2 this_class 类索引
u2 super_class 父类索引
u2 interfaces_count 接口个数
u2 interfaces[interfaces_count] 接口类索引信息
u2 fields_count 字段数
field_info fields[fields_count] 字段表信息
u2 methods_count 方法数
method_info methods[methods_count] 方法表信息
u2 attributes_count 属性个数
attribute_info attributes[attributes_count] 属性表信息

魔数

魔数 CAFE BABE 是 JVM 识别 .class 文件的标志,虚拟机在加载文件之前会先检查这4个字节

版本号

0000 0034
0000 表示 minor version,次版本号
0034 表示 major version,主版本号,十进制是52,表示采用的是jdk1.8

常量池

0020 表示常量池的大小为32,实际索引为 1~31,常量池计数是从1而不是0开始。常量池中主要存放2大类常量:字面量(Literal)和符号引用(Symbolic References)。
字面量主要指文本字符串、被声明为final的常量值。
符号引用主要包括以下3类:

  • 类和接口的全限定名
  • 字段的名称和描述符
  • 方法的名称和描述符

常量池中的每个常量都是一个表,共有11种不同的表结构,它们有一个共同的特点,就是表开始的第一位都是一个u1类型的标志位(tag,取值为1到12,缺少标志为2的数据类型)。tag表示的数据类型如下表所示:

类型 标志 描述
CONSTANT_Utf8_info 1 UTF-8编码的字符串
CONSTANT_Integer_info 3 整形字面量,boolean、byte、char、short等类型都用int存放
CONSTANT_Float_info 4 浮点型字面量
CONSTANT_Long_info 5 长整型字面量
CONSTANT_Double_info 6 双精度浮点型字面量
CONSTANT_Class_info 7 类或接口的符号引用
CONSTANT_String_info 8 字符串类型字面量
CONSTANT_Fieldref_info 9 字段的符号引用
CONSTANT_Methodref_info 10 类中方法的符号引用
CONSTANT_InterfaceMethodref 11 接口中方法的符号引用
CONSTANT_NameAndType_info 12 字段或方法的部分符号引用
CONSTANT_MethodHandle_info 15 表示方法句柄
CONSTANT_MethodType_info 16 表示方法类型
CONSTANT_InvokeDynamic_info 18 表示invokedynamic指令

以下是一份常量池数据结构总表:

常量 项目 类型 描述
CONSTANT_Utf8_info tag u1 值为1
length u2 UTF-8编码的字符串占用了字节数
bytes u1 长度为length的UTF-8编码的字符串
CONSTANT_Integer_info tag u1 值为3
bytes u4 按照高位在前存储的int值
CONSTANT_Float_info tag u1 值为4
bytes u4 按照高位在前存储的float值
CONSTANT_Long_info bytes u1 值为5
tag u8 按照高位在前存储的long值
CONSTANT_Double_info tag u1 值为6
bytes u8 按照高位在前存储的double值
CONSTANT_Class_info tag u1 值为7
index u2 指向全限定名常量项的索引
CONSTANT_String_info tag u1 值为8
index u2 指向字符串字面量的索引
CONSTANT_Fieldref_info tag u1 值为9
class_index u2 指向声明字段的类或接口描述符CONSTANT_Class_info的索引项
name_and_type_index u2 指向字段描述符CONSTANT_NameAndType的索引项
CONSTANT_Methodref_info tag u1 值为10
class_index u2 指向声明方法的类描述符CONSTANT_Class_info的索引项
name_and_type_index u2 指向名称及类型描述符CONSTANT_NameAndType的索引项
CONSTANT_InterfaceMethodref_info tag u1 值为11
class_index u2 指向声明方法的接口描述符CONSTANT_Class_info的索引项
name_and_type_index u2 指向名称及类型描述符CONSTANT_NameAndType的索引项
CONSTANT_NameAndType_info tag u1 值为12
name_index u2 指向该字段或方法名称常量项的索引
descriptor_index u2 指向该字段或方法描述符常量项的索引
CONSTANT_MethodHandle_info tag u1 值为15
reference_kind u2 值必须在1一9之间(包括1和9).它决定了方法句柄的类型。方法句柄类型的值表示方法句柄的字节码行为
reference_index u2 值须是对常量池的有效索引
CONSTANT_MethodType_info tag u1 值为16
descriptor_index u2 值必须是对常量池的有效索引,常量池在该索引处的项必须是CONSTANT_Utt8_info结构,表示方法的描述符
CONSTANT_InvokeDynamic_info tag u1 值为18
bootstrap_method_attr_index u2 值必须是对当前Cass文件中引导方法表的bootstrap_methodsl[]数组的有效索引
name_and_type_index u2 值必须是对当前常量池的有效索引,常量池在该索引处的项必须是CONSTANT_NameAndType_info结构,表示方法名和方法描述符

常量池具体规则分析,另开一篇分析,此处不做过多描述

访问标记

常量池之后,是2个字节来表示访问标志,用于识别一些类或者接口层次的访问信息

标志名称 标志值 含义
ACC_PUBLIC 0x0001 是否为public类型
ACC_FINAL 0x0010 是否被声明为final,只有类可以设置
ACC_SUPER 0x0020 JDK1.0.2以后这个标志都为真,不再使用
ACC_INTERFACE 0x0200 标识这是一个接口
ACC_ABSTRACT 0x0400 是否为abstract类型,对于接口或抽象类来说,此标志值为真,其他类值为假
ACC_SYNTHETIC 0x1000 标识这个类并非由用户代码产生
ACC_ANNOTATION 0x2000 标识这是一个注解
ACC_ENUM 0x4000 标识这是一个枚举

针对这个类来说,其访问标志应该是ACC_PUBLIC、ACC_SUPER这2个标志为真,所以其值为 0x0001 | 0x0020 = 0x0021,上述16进制数据 5600 2100 中的 00 21

类索引、父类索引

访问标志后的2个字节0004 = 4,指向常量池里的第4个常量,接下来的0006 = 6,指向常量池里的第6个常量(该类的父类是Object)

  1. #4 = Class #27 // Rectangle
  2. #6 = Class #29 // java/lang/Object
  3. #27 = Utf8 Rectangle
  4. #29 = Utf8 java/lang/Object

接口索引

接口个数 00 01 表示只有一个接口,00 07 标识第一个接口指向 第7个常量

  1. #7 = Class #30 // Shape
  2. #30 = Utf8 Shape

字段表

接口索引后面紧跟着的是字段表信息,用于描述接口或类中声明的变量,不包括方法内部的变量,字段表的入口前2个字节表示字段的个数,00 03 表示有3个字段(TAG、width、length),后面紧跟着的是字段的描述表。字段信息接口表如下:

类型 描述 备注
u2 access_flags 记录字段的访问标志
u2 name_index 常量池中的索引项,指定字段的名称
u2 descriptor_index 常量池中的索引项,指定字段的描述符
u2 attributes_count 包含的attribute数
attribute_info attributes[attributes_count] 每一个attributes的具体结构

其中 access_flags 字段访问标识如下:

权限名称 描述
ACC_PUBLIC 0x0001 public
ACC_PRIVATE 0x0002 private
ACC_PROTECTED 0x0004 protected
ACC_STATIC 0x0008 static,静态
ACC_FINAL 0x0010 final
ACC_VOLATILE 0x0040 volatile,不可和ACC_FIANL一起使用
ACC_TRANSIENT 0x0080 在序列化中被忽略的字段
ACC_SYNTHETIC 0x1000 由编译器产生,不存在于源代码中
ACC_ENUM 0x4000 枚举类型

紧随access_flags标志的是name_index和descriptor_index,他们都是对常量池的引用。name_index代表着字段的简单名称,descriptor_index代表着字段的描述符。描述符的作用是用来描述字段的数据类型、方法的参数列表(包括数量、类型以及顺序)和返回值。

描述符标识字符含义:

标识字符 含义
B 基本类型byte
C 基本类型char
D 基本类型double
F 基本类型float
I 基本类型int
J 基本类型long
S 基本类型short
Z 基本类型boolean
V 特殊类型void
L 对象类型,如Ljava/lang/Object;
[ 数组类型,多个维度则有多个[

用描述符来描述方法时,按照先参数列表后返回值的顺序描述,参数列表按照参数的严格顺序放在一组“()”之内

以上文中 TAG 字段为例分析,涉及相关数据为:

00 1A00 0800 0900 0100 0A00 0000 0200 05

  • 00 1A 表示 access_flags,private static final 值为 0x0002 | 0x00008 | 0x0010 = 0x001A
  • 00 08 表示 name_index,值为 TAG

    #8 = Utf8 TAG

  • 00 09 表示 descriptor_index

    #9 = Utf8 Ljava/lang/String;

  • 00 01 表示有一个属性

  • 00 0A 表示属性name,attribute_name_index 值为 0x0A = 10,为 ConstantValue 属性,还有其他的属性集合,后文分析

    #10 = Utf8 ConstantValue

ConstantValue属性的结构为

  1. ConstantValue_attribute {
  2. u2 attribute_name_index;
  3. u4 attribute_length;
  4. u2 constant_value_index;
  5. }

attribute_length 在 ConstantValue_attribute 属性值固定为 02,因为接下来的内容值只有2个字节大小(常量值索引)

所以 TAG 字段表中 ConstantValue 属性的常量值索引为:0x05

  1. #5 = String #28 // 矩形

综上,则 TAG 定义为:private static final String TAG = “矩形”;

方法表

方法表的结构与字段表的结构是一样的

类型 描述 备注
u2 access_flags 记录方法的访问标志
u2 name_index 常量池中的索引项,指定方法的名称
u2 descriptor_index 常量池中的索引项,指定方法的描述符
u2 attributes_count 包含的attribute数
attribute_info attributes[attributes_count] 每一个attributes的具体结构

方法访问标识比字段访问标识丰富,一共有12种

方法访问标记 特征值 描述
ACC_PUBLIC 0x0001 public
ACC_PRIVATE 0x0002 private
ACC_PROTECTED 0x0004 protected
ACC_STATIC 0x0008 static
ACC_FINAL 0x0010 final
ACC_SYNCHRONIZED 0x0020 synchronized
ACC_BRIDGE 0x0040 bridge 方法, 由编译器生成
ACC_VARARGS 0x0080 方法包含可变长度参数,比如 String… args
ACC_NATIVE 0x0100 native
ACC_ABSTRACT 0x0400 abstract
ACC_STRICT 0x0800 声明为 strictfp,表示使用 IEEE-754 规范的精确浮点数,极少使用
ACC_SYNTHETIC 0x1000 表示这个方法是由编译器自动生成,而不是用户代码编译产生

0x0003 表示有3个方法,这里以 getName() 方法为例分析,相关数据为:

  1. 0001 0014 0015 0001 0010 0000 001B 0001 0001 0000 0003 1205 B000 0000 0100 100 0000 0600 0100 0000 18
  • access_flags 值为 0x0001,表示这是一个 public 方法
  • name_index 值为 0x0014,换算为10进制是20

    1. #20 = Utf8 getName
  • descriptor_index 值为 0x0015,换算为10进制是21

    1. #21 = Utf8 ()Ljava/lang/String;
  • 表计数器为0x0001,表示只有一个属性,接下来就是该方法的第一个属性表。第一个属性表对应的属性名称索引值为0x0010,十进制为16,对应常量值为“Code”,说明此属性是方法的字节码描述,这个属性就存储了方法里的java代码编译后的字节码指令,具体字节码指令不再分析

    1. #16 = Utf8 Code

    属性表

    在Class文件、字段表、方法表中都可以携带自己的属性表集合,以用于描述某些场景专有的信息

属性名称 使用位置 含义
Code 方法表 Java代码编译成的字节码指令
ConstantValue 字段表 final关键字定义的常量值
Deprecated 类、方法表、字段表 被声明为deprecated的方法和字段
Exceptions 方法表 方法抛出的异常
InnerClasses 类文件 内部类列表
LineNumberTable Code属性 Java源码的行号与字节码指令的对应关系
LocalVariableTable Code属性 方法的局部变量描述
SourceFile 类文件 源文件名称
Synthetic 类、方法表、字段表 标识方法或字段为编译器自动生成的

具体属性内容单开一篇文章分析,例中属性表相关数据为:

  1. 00 0100 1600 0000 0200 17
  • 0x0001 表示此例中只有 1个属性
  • 0x0016 换算10进制的值为 22,表示 attribute_name_index = 22,表示属性为SourceFile 属性

    1. #22 = Utf8 SourceFile
  • 0x00000002 表示 attribute_length, 值为 2

  • 0x0017 表示sourcefile_name_index,换算为10进制为 23
    1. #23 = Utf8 Rectangle.java
    截止至此,整个 Class 的结构就算结束了

参考&致谢: Java Class文件结构解析

工具推荐

推荐一个查看分析字节码的工具 010 Editor,配合 Template CLASSAdv.bt 查看字节码
Class文件结构 - 图1

当前定位到值是类访问标识属性,可以看到16进制数据中的位置,以及字段结构,10进制转换等一系列方便的操作