持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第2天
Java 类文件结构
本文主要介绍一下Java类的文件结构组成及字节码常用查看方式及工具。
1、Class类结构
单个类文件的结构体定义如下。
ClassFile {
u4 magic;
u2 minor_version;
u2 major_version;
u2 constant_pool_count;
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];
}
其中u1、u2、u4分别代表1、2、4个字节无符号数(无符号数是基本数据类型,主要可以用来描述数字、索引符号、数量值或者按照UTF-8编码构成的字符串值,大小使用u1、u2、u4、u8分别表示1字节、2字节、4字节和8字节。)。
2、结构说明
2.1、magic:
魔数,魔数的唯一作用是确定这个文件是否为一个能被虚拟机所接受的Class文件。魔数值固定为0xCAFEBABE,不会改变。
minor_version、major_version:
分别为Class文件的副版本和主版本。它们共同构成了Class文件的格式版本号。不同版本的虚拟机实现支持的Class文件版本号也相应不同,高版本号的虚拟机可以支持低版本的Class文件,反之则不成立。
2.2、constant_pool_count:
常量池计数器,constant_pool_count的值等于constant_pool表中的成员数加1。
2.3、constant_pool[]:
常量池,constant_pool是一种表结构,它包含Class文件结构及其子结构中引用的所有字符串常量、类或接口名、字段名和其它常量。常量池不同于其他,索引从1开始到constant_pool_count -1。
2.4、access_flags:
访问标志,access_flags是一种掩码标志,用于表示某个类或者接口的访问权限及基础属性。access_flags的取值范围和相应含义见下表:
| 标志名称 | 值 | 含义 | 作用对象 |
|---|---|---|---|
| ACC_PUBLIC | 0x0001 | public类型 | 类和接口 |
| ACC_FINAL | 0x0010 | 类为final类型 | 只有类 |
| ACC_SUPER | 0x0020 | 使用新型的invokespecial寓意,JDK1.2之后编译出来为真 | 类和接口 |
| ACC_INTERFACE | 0x0200 | 接口类型,不是类类型 | 所有的接口,没有类 |
| ACC_ABSTRACT | 0x0400 | 抽象类型 | 所有的接口,部分类 |
| ACC_SYNTHETIC | 0x1000 | 标识这个类并非有用户代码产生的 | |
| ACC_ANNOTATION | 0x2000 | 标识注解 | |
| ACC_ENUM | 0x4000 | 标识枚举 |
2.5、this_class:
类索引,this_class的值必须是对constant_pool表中项目的一个有效索引值。constant_pool表在这个索引处的项必须为CONSTANT_Class_info类型常量,表示这个Class文件所定义的类或接口。
2.6、super_class:
父类索引,对于类来说,super_class的值必须为0或者是对constant_pool表中项目的一个有效索引值。如果它的值不为0,那 constant_pool表在这个索引处的项必须为CONSTANT_Class_info类型常量,表示这个Class文件所定义的类的直接父类。当 然,如果某个类super_class的值是0,那么它必定是java.lang.Object类,因为只有它是没有父类的。
2.7、interfaces_count:
接口计数器,interfaces_count的值表示当前类或接口的直接父接口数量。
2.8、interfaces[]:
接口表,interfaces[]数组中的每个成员的值必须是一个对constant_pool表中项目的一个有效索引值,它的长度为interfaces_count。每个成员interfaces[i] 必须为CONSTANT_Class_info类型常量。
2.9、fields_count:
字段计数器,fields_count的值表示当前Class文件fields[]数组的成员个数。
2.10、fields[]:
字段表,fields[]数组中的每个成员都必须是一个fields_info结构的数据项,用于表示当前类或接口中某个字段的完整描述。
2.11、methods_count:
方法计数器,methods_count的值表示当前Class文件methods[]数组的成员个数。
2.12、methods[]:
方法表,methods[]数组中的每个成员都必须是一个method_info结构的数据项,用于表示当前类或接口中某个方法的完整描述。
2.13、attributes_count:
属性计数器,attributes_count的值表示当前Class文件attributes表的成员个数。
2.14、attributes[]:
属性表,attributes表的每个项的值必须是attribute_info结构。
3、示例
3.1、源码
public class AddDemo {
public static void main(String[] args) {
int a = 1;
int b = 2;
int c= a+b;
System.out.println("c=" + c);
}
}
3.2、CLASS文件的十六进制文件
IDEA中需要安装BinED插件,左边是16进制码,右边是转成10进制后对应的ASCII码值。可参考ASCII,一般人看到就懵逼了,我也很懵逼,表示看不懂。
3.3 、通过javap命令查看类字节码
如果想看懂可以通过jdk自带的javap命令进行查看,通过javap -v 类绝对路径/AddDemo.class即可输出。相关指令信息请参考JVM操作码指令
Classfile /Users/zhangjian/works/workspace_home/docker-demo/target/classes/com/jianjang/docker/study/base/AddDemo.class
Last modified 2022-10-19; size 876 bytes
MD5 checksum bad782a180920f88bf4a661d879fb8bf
Compiled from "AddDemo.java"
public class com.jianjang.docker.study.base.AddDemo
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #11.#30 // java/lang/Object."<init>":()V
#2 = Fieldref #31.#32 // java/lang/System.out:Ljava/io/PrintStream;
#3 = Class #33 // java/lang/StringBuilder
#4 = Methodref #3.#30 // java/lang/StringBuilder."<init>":()V
#5 = String #34 // c=
#6 = Methodref #3.#35 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#7 = Methodref #3.#36 // java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
#8 = Methodref #3.#37 // java/lang/StringBuilder.toString:()Ljava/lang/String;
#9 = Methodref #38.#39 // java/io/PrintStream.println:(Ljava/lang/String;)V
#10 = Class #40 // com/jianjang/docker/study/base/AddDemo
#11 = Class #41 // java/lang/Object
#12 = Utf8 <init>
#13 = Utf8 ()V
#14 = Utf8 Code
#15 = Utf8 LineNumberTable
#16 = Utf8 LocalVariableTable
#17 = Utf8 this
#18 = Utf8 Lcom/jianjang/docker/study/base/AddDemo;
#19 = Utf8 main
#20 = Utf8 ([Ljava/lang/String;)V
#21 = Utf8 args
#22 = Utf8 [Ljava/lang/String;
#23 = Utf8 a
#24 = Utf8 I
#25 = Utf8 b
#26 = Utf8 c
#27 = Utf8 MethodParameters
#28 = Utf8 SourceFile
#29 = Utf8 AddDemo.java
#30 = NameAndType #12:#13 // "<init>":()V
#31 = Class #42 // java/lang/System
#32 = NameAndType #43:#44 // out:Ljava/io/PrintStream;
#33 = Utf8 java/lang/StringBuilder
#34 = Utf8 c=
#35 = NameAndType #45:#46 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#36 = NameAndType #45:#47 // append:(I)Ljava/lang/StringBuilder;
#37 = NameAndType #48:#49 // toString:()Ljava/lang/String;
#38 = Class #50 // java/io/PrintStream
#39 = NameAndType #51:#52 // println:(Ljava/lang/String;)V
#40 = Utf8 com/jianjang/docker/study/base/AddDemo
#41 = Utf8 java/lang/Object
#42 = Utf8 java/lang/System
#43 = Utf8 out
#44 = Utf8 Ljava/io/PrintStream;
#45 = Utf8 append
#46 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;
#47 = Utf8 (I)Ljava/lang/StringBuilder;
#48 = Utf8 toString
#49 = Utf8 ()Ljava/lang/String;
#50 = Utf8 java/io/PrintStream
#51 = Utf8 println
#52 = Utf8 (Ljava/lang/String;)V
{
public com.jianjang.docker.study.base.AddDemo();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 11: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/jianjang/docker/study/base/AddDemo;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=4, args_size=1
0: iconst_1
1: istore_1
2: iconst_2
3: istore_2
4: iload_1
5: iload_2
6: iadd
7: istore_3
8: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
11: new #3 // class java/lang/StringBuilder
14: dup
15: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
18: ldc #5 // String c=
20: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
23: iload_3
24: invokevirtual #7 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
27: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
30: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
33: return
LineNumberTable:
line 13: 0
line 14: 2
line 15: 4
line 16: 8
line 17: 33
LocalVariableTable:
Start Length Slot Name Signature
0 34 0 args [Ljava/lang/String;
2 32 1 a I
4 30 2 b I
8 26 3 c I
MethodParameters:
Name Flags
args
}
SourceFile: "AddDemo.java"
3.4、通过IDE插件jclasslib Bytecode Viewer
也可以通过安装IDE傻瓜式插件jclasslib Bytecode Viewer进行查看。使用方法
- 选择要查看的class文件
- 打开“view” 菜单,选择“Show Bytecode With jclasslib” 选项。
- 选择上述菜单项后 IDEA 中会弹出 jclasslib 工具窗口