Java字节码文件结构
查看字节码的方法
- 使用javap命令。如
javap -v Person.class 输出字节码信息,javap -v -p Person.class 输出完整字节码信息。 - 使用
jclasslib 插件。
字节码文件标识说明
类访问标记
标记名 | 说明 |
---|
ACC_PUBLIC | 是否为public | ACC_FINAL | 是否为final | ACC_SUPER | 兼容旧版本,Java1.1及以上版本始终设置该值 | ACC_INTERFACE | 是否为接口 | ACC_ABSTRACT | 是否为抽象,接口和抽象类有此标记 | ACC_SYNTHETIC | 这个类的代码不是用户代码 | ACC_ANNOTATION | 是否为注解 | ACC_ENUM | 是否为枚举 |
字段类型标识符
标识字符 | 说明 |
---|
B | 原始数据类型byte | C | 原始数据类型char | D | 原始数据类型double | F | 原始数据类型float | I | 原始数据类型int | J | 原始数据类型long | S | 原始数据类型short | Z | 原始数据类型boolean | V | 特殊类型void | L | 引用数据类型,以分号“;”结尾 | [ | 一维数组 |
字段标识符
字段的标识符与类的非常类似。
标识符 | 说明 |
---|
ACC_PUBLIC | 是否为public | ACC_PRIVATE | 是否为private | ACC_PROTECTED | 是否为protected | ACC_STATIC | 是否为static | ACC_FINAL | 是否为final | ACC_VOLATILE | 是否为volatile | ACC_TRANSIENT | 是否序列化 | ACC_SYNTHETIC | 是否由编译器自动生成 | ACC_ENUM | 是否为枚举 |
方法标识符
标识符 | 说明 |
---|
ACC_PUBLIC | 是否为public | ACC_PRIVATE | 是否为private | ACC_PROTECTED | 是否为protected | ACC_STATIC | 是否为static | ACC_FINAL | 是否为final | ACC_SYNCHRONIZED | 是否为synchronized | ACC_NATIVE | 是否为本地方法 | ACC_SYNTHETIC | 是否由编译器自动生成 | ACC_ABSTRACT | 是否为抽象方法 | ACC_STRICT | 限制浮点计算以确保可移植性 | ACC_VARARGS | 是否能接收可变参数 |
字节码文件结构说明
编译生产字节码
public class Person {
private String name = "小明";
private int age = 18;
private final int NO = 10068;
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
说明:将Person类进行编译,生成Person.class 文件,打开文件可以看到里面的内容。
- 开头是
CAFE BABE ,这4个字节称为默数,是字节码文件的身份标识,只有以CA FE BA BE 开头的class文件才能被JVM接受。 - 再往后是
00 00 表示Java的次版本号,00 34 表示主版本号。 - 再往后就是字符串常量池。
反编译字节码文件
Java内置javap 命令可以反编译查看字节码文件,或者通过jclasslib 插件也可以查看。
通过javap -v Person.class 命令查看输出信息:
Classfile /D:/TestWorkspace/AndroidDemo/lib_java/build/classes/java/main/com/example/lib_java/Person.class
Last modified 2022-3-3; size 624 bytes
MD5 checksum 44c9bd227e49c0b8440e0f10143b753d
Compiled from "Person.java"
public class com.example.lib_java.Person
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #7.#28 // java/lang/Object."<init>":()V
#2 = String #29 // 小明
#3 = Fieldref #6.#30 // com/example/lib_java/Person.name:Ljava/lang/String;
#4 = Fieldref #6.#31 // com/example/lib_java/Person.age:I
#5 = Fieldref #6.#32 // com/example/lib_java/Person.NO:I
#6 = Class #33 // com/example/lib_java/Person
#7 = Class #34 // java/lang/Object
#8 = Utf8 name
#9 = Utf8 Ljava/lang/String;
#10 = Utf8 age
#11 = Utf8 I
#12 = Utf8 NO
#13 = Utf8 ConstantValue
#14 = Integer 10068
#15 = Utf8 <init>
#16 = Utf8 ()V
#17 = Utf8 Code
#18 = Utf8 LineNumberTable
#19 = Utf8 LocalVariableTable
#20 = Utf8 this
#21 = Utf8 Lcom/example/lib_java/Person;
#22 = Utf8 getName
#23 = Utf8 ()Ljava/lang/String;
#24 = Utf8 getAge
#25 = Utf8 ()I
#26 = Utf8 SourceFile
#27 = Utf8 Person.java
#28 = NameAndType #15:#16 // "<init>":()V
#29 = Utf8 小明
#30 = NameAndType #8:#9 // name:Ljava/lang/String;
#31 = NameAndType #10:#11 // age:I
#32 = NameAndType #12:#11 // NO:I
#33 = Utf8 com/example/lib_java/Person
#34 = Utf8 java/lang/Object
{
private java.lang.String name;
descriptor: Ljava/lang/String;
flags: ACC_PRIVATE
private int age;
descriptor: I
flags: ACC_PRIVATE
private final int NO;
descriptor: I
flags: ACC_PRIVATE, ACC_FINAL
ConstantValue: int 10068
public com.example.lib_java.Person();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: ldc #2 // String 小明
7: putfield #3 // Field name:Ljava/lang/String;
10: aload_0
11: bipush 18
13: putfield #4 // Field age:I
16: aload_0
17: sipush 10068
20: putfield #5 // Field NO:I
23: return
LineNumberTable:
line 3: 0
line 4: 4
line 5: 10
line 6: 16
LocalVariableTable:
Start Length Slot Name Signature
0 24 0 this Lcom/example/lib_java/Person;
public java.lang.String getName();
descriptor: ()Ljava/lang/String;
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #3 // Field name:Ljava/lang/String;
4: areturn
LineNumberTable:
line 9: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/example/lib_java/Person;
public int getAge();
descriptor: ()I
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #4 // Field age:I
4: ireturn
LineNumberTable:
line 13: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/example/lib_java/Person;
}
SourceFile: "Person.java"
第1部分:字节码的描述
Classfile /D:/*/*/*/build/classes/java/main/com/example/lib_java/Person.class
Last modified 2022-3-3; size 560 bytes
MD5 checksum ed1b841f2fb00defe07401da1d8c129f
Compiled from "Person.java"
依次说明:
- 字节码文件的存放位置。
- 字节码文件修改日期和文件大小。
- 生成唯一值MD5。
- 生成字节码文件的源文件是
Person.java 。
第2部分:类的描述
public class com.example.lib_java.Person
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
依次说明:
- 字节码文件的类全名。
- 次版本号。
- 主版本号。
- 类访问标记,一共8种。
第3部分:常量池
第3部分是常量池,是字节码文件的重要部分,主要存放两类信息:
- 字面量:字符串、final常量等;
- 符合引用:类和接口的全限定名、字段的名称和描述符、方法的名称和描述符。
注:
# 号后面是索引,索引从1开始,索引没有从0开始,是因为设计者考虑到不引用任何常量时,可以将索引设置为0来表示。= 号后面是常量的类型。
Constant pool:
#1 = Methodref #7.#28 // java/lang/Object."<init>":()V
#2 = String #29 // 小明
#3 = Fieldref #6.#30 // com/example/lib_java/Person.name:Ljava/lang/String;
#4 = Fieldref #6.#31 // com/example/lib_java/Person.age:I
#5 = Fieldref #6.#32 // com/example/lib_java/Person.NO:I
#6 = Class #33 // com/example/lib_java/Person
#7 = Class #34 // java/lang/Object
#8 = Utf8 name
#9 = Utf8 Ljava/lang/String;
#10 = Utf8 age
#11 = Utf8 I
#12 = Utf8 NO
#13 = Utf8 ConstantValue
#14 = Integer 10068
#15 = Utf8 <init>
#16 = Utf8 ()V
#17 = Utf8 Code
#18 = Utf8 LineNumberTable
#19 = Utf8 LocalVariableTable
#20 = Utf8 this
#21 = Utf8 Lcom/example/lib_java/Person;
#22 = Utf8 getName
#23 = Utf8 ()Ljava/lang/String;
#24 = Utf8 getAge
#25 = Utf8 ()I
#26 = Utf8 SourceFile
#27 = Utf8 Person.java
#28 = NameAndType #15:#16 // "<init>":()V
#29 = Utf8 小明
#30 = NameAndType #8:#9 // name:Ljava/lang/String;
#31 = NameAndType #10:#11 // age:I
#32 = NameAndType #12:#11 // NO:I
#33 = Utf8 com/example/lib_java/Person
#34 = Utf8 java/lang/Object
依次说明:
- #1常量的类型是Methodref,表示用来定义方法的,#1常量指向常量池中#7和#28的常量。
- #7常量类型是Class,表示用来定义类或接口,指向#34常量。
- #34常量类型是Utf8的字符串,值为
java/lang/Object 。 - #28常量类型是NameAndType,表示字段或方法的部分符号引用,指向常量池中的#15和#16常量。
- #15常量类型是Utf8的字符串,值为
<init> ,表示为构造方法。 - #16常量类型是Utf8的字符串,值为
()V ,表示方法的返回值是void。
总结#1常量,Person类的使用默认的构造方法,来源自Object类。
- #2常量的类型是String,指向#29常量。
- #29常量的类型是Utf8的字符串,值为小明。
总结#2常量,定义类一个为“小明”的字符串。
- #3常量的类型是Fieldref,表示用来定义字段的,指向#6和#30常量。
- #6常量的类型是Class,指向#33常量。
- #33常量的类型是Utf8的字符串,值是
com/example/lib_java/Person 。 - #30常量的类型是NameAndType,指向 #8和#9常量。
- #8常量的类型是Utf8的字符串,值是
name 。 - #9常量的类型是Utf8的字符串,值是
Ljava/lang/String 。
总结#3常量,声明了一个类型为String的字段name。
第4部分:字段表
字段表是用于描述类或接口中声明的字段,包含类变量和成员变量,不包含局部变量。
private java.lang.String name;
descriptor: Ljava/lang/String;
flags: ACC_PRIVATE
依次说明:
- 表示该字段的访问修饰符是
private ,类型是String ,字段名是name 。
第5部分:方法表
方法表用于描述类或接口中声明的方法。
public com.example.lib_java.Person();
descriptor: ()V
flags: ACC_PUBLIC
依次说明:
- 表示该方法是构造方法,返回值类型是
void ,访问修饰符是public 。
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: ldc #2 // String 小明
7: putfield #3 // Field name:Ljava/lang/String;
10: aload_0
11: bipush 18
13: putfield #4 // Field age:I
16: aload_0
17: sipush 10068
20: putfield #5 // Field NO:I
23: return
LineNumberTable:
line 3: 0
line 4: 4
line 5: 10
line 6: 16
LocalVariableTable:
Start Length Slot Name Signature
0 24 0 this Lcom/example/lib_java/Person;
依次说明:
- stack:最大操作数占,JVM在运行时会根据这个值分配栈帧。为什么是2,因为会调用父类的构造方法。
- locals:局部变量数量。隐藏了this变量。
- args_size:形参数量。默认传入this变量。
- LineNumberTable:描述源码行号与字节码行号之间的对应关系。
- LocalVariableTable:描述栈帧中的局部变量与源码中定义的变量之间的关系,其中的this。
|