1 学习目标
- 什么是字节码
- 字节码的结构
- 如何查看字节码
2 什么是字节码
- 是一种包含执行程序、由一序列 op 代码(操作码)/数据对组成的二进制文件,字节流组成生成的
.class文件,虚拟机的文件格式,字节码是一种中间码,便于跨平台运行。
3 Class文件
- Class文件是一组以
8位字节为基础单位的二进制流,各项数据无添加分隔符严格按照顺序紧凑排布在Class文件之中。 - 内部结构只包含两种数据结构:(view和ViewGroup理解)
无符号整数(u%),类型u1、u2和u4分别表示无符号的一、二或四字节数量表(%_info),包含无符号整数或者其他表数据
官方文档声明的.class结构图,
ClassFile {
u4 magic; // 魔数,标致一个class文件
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]; //属性数组
}
描述符
是表示字段或方法类型的字符串
例如类相关名称的叫法
* 类名:`java.lang.String`
* 类的内部名称:`java/lang/String`
* 类的类型描述符:`Ljava/lang/String;`
* 方法描述符:`([Ljava/lang/String)V`(比如`main`方法的描述符)
常量池
可以理解为Class文件的资源仓库,主要存放两大类常量
字面量: 相当于语言级别的常量概念,如文本字符串、声明为final的常量值符号引用,编译原理方面的概念,三类常量- 类和接口的限定符
- 字段的名称和描述符
- 方法的名称和描述符
常量池下的表格数据都具有以下一般格式
cp_info {
u1 tag; // 标志位,代表哪类型的数据
u1 info[]; // 根据标志位的不同,info数组的内容随标签的值而变化
}
常量类型 |tag |
| ----------------------------- | -- |
| `CONSTANT_Class` | 7 | // 类或接口的符号引用
| `CONSTANT_Fieldref` | 9 | // 字段的符号引用
| `CONSTANT_Methodref` | 10 | //类中方法的符号引用
| `CONSTANT_InterfaceMethodref` | 11 | //接口中方法符号引用
| `CONSTANT_String` | 8 | //字符串类型字面量
| `CONSTANT_Integer` | 3 |
| `CONSTANT_Float` | 4 |
| `CONSTANT_Long` | 5 |
| `CONSTANT_Double` | 6 |
| `CONSTANT_NameAndType` | 12 | //用于表示一个字段或方法,不指明它属于哪个类或接口类型
| `CONSTANT_Utf8` | 1 | // utf-8编码的字符串
| `CONSTANT_MethodHandle` | 15 | //表示方法句柄
| `CONSTANT_MethodType` | 16 | //标识方法类型
| `CONSTANT_InvokeDynamic` | 18 //表示一个动态方法调用点
以`CONSTANT_Methodref` 为例子, 一个类方法的信息有什么; 1:属于哪个类 ,2:方法描述
CONSTANT_Methodref_info {
u1 tag; // 这里的值是10
u2 class_index; // 指向一个tag7的值的位置
u2 name_and_type_index; // 指向tag12的值的位置
}
CONSTANT_Class_info {
u1 tag; // 这里的值是7
u2 name_index; // 指向一个tag1的值的字符串
}
CONSTANT_NameAndType_info {
u1 tag; // 这里的值是15
u2 name_index; // 指向一个tag1的值的字符串
u2 descriptor_index; // 指向method_info方法表的位置
}
字段表
用来描述接口或者类中声明的变量 (不包含方法内部声明的局部变量)
- 类级变量 (静态变量)
- 实例变量
field_info {
u2 // 访问标志;
u2 // 名称索引;
u2 // 描述符索引;
u2 // 属性计数;
attribute_info attributes[attributes_count]; //属性表数组
}
方法表
method_info {
u2 // 访问标志;
u2 // 名称索引
u2 // 描述符索引
u2 // 属性计数
attribute_info attributes[attributes_count]; //属性数组
}
属性表
Class文件、字段表、方法表可有携带自己的属性表集合,用于描述某些场景特有的信息
attribute_info {
u2 // 属性名索引;
u4 // 属性长度;
u1 info[attribute_length]; 属性数组
}
最重要的属性Code
Code属性是method_info结构的属性表中的一个变长属性
Code_attribute {
u2 attribute_name_index; //属性名索引; 固定是Code
u4 attribute_length; //属性长度;
u2 max_stack; //最大操作数栈深度; 虚拟机用来分配栈帧
u2 max_locals; //局部遍历表所需要的存储空间;
u4 code_length; // 字节码长度;
u1 code[code_length]; //字节码指令数组
u2 exception_table_length;
{ u2 start_pc;
u2 end_pc;
u2 handler_pc;
u2 catch_type;
} exception_table[exception_table_length];
u2 attributes_count;
attribute_info attributes[attributes_count];
}
4.查看字节码
public class ByteCodeTest {
public static void main(String[] args) {
System.out.println("Hello World!");
}
}
- 编译:
javac -g ByteCodeTest.java - 查看字节码:
javap -p -v ByteCodeTest.class
Last modified 2022年10月10日; size 540 bytes
MD5 checksum c3a263c41a090ffd88d4fb7f710e1676
Compiled from "ByteCodeTest.java"
public class ByteCodeTest //类名
minor version: 0
major version: 55
flags: (0x0021) ACC_PUBLIC, ACC_SUPER //类修饰符
this_class: #5 // ByteCodeTest
super_class: #6 // java/lang/Object
interfaces: 0, fields: 0, methods: 2, attributes: 1
Constant pool: //常量值,字面量和符号引用
#1 = Methodref #6.#20 // java/lang/Object."<init>":()V
#2 = Fieldref #21.#22 // java/lang/System.out:Ljava/io/PrintStream;
#3 = String #23 // Hello World!
#4 = Methodref #24.#25 // java/io/PrintStream.println:(Ljava/lang/String;)V
#5 = Class #26 // ByteCodeTest
#6 = Class #27 // java/lang/Object
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 LByteCodeTest;
#14 = Utf8 main
#15 = Utf8 ([Ljava/lang/String;)V
#16 = Utf8 args
#17 = Utf8 [Ljava/lang/String;
#18 = Utf8 SourceFile
#19 = Utf8 ByteCodeTest.java
#20 = NameAndType #7:#8 // "<init>":()V
#21 = Class #28 // java/lang/System
#22 = NameAndType #29:#30 // out:Ljava/io/PrintStream;
#23 = Utf8 Hello World!
#24 = Class #31 // java/io/PrintStream
#25 = NameAndType #32:#33 // println:(Ljava/lang/String;)V
#26 = Utf8 ByteCodeTest
#27 = Utf8 java/lang/Object
#28 = Utf8 java/lang/System
#29 = Utf8 out
#30 = Utf8 Ljava/io/PrintStream;
#31 = Utf8 java/io/PrintStream
#32 = Utf8 println
#33 = Utf8 (Ljava/lang/String;)V
{
public ByteCodeTest();
descriptor: ()V //构造方式描述符
flags: (0x0001) ACC_PUBLIC
Code: //方法code属性,也是代码区
// 操作数栈深度,占用的Slot(变量槽)的大小(long,double占2个,其余1个),方法参数
stack=1, locals=1, args_size=1
// 0表示aload_0指令在代码数组中的下标,aload_0表示加载第0个变量槽位置上的对象,压入操作数栈顶
0: aload_0
// 以栈顶变量为接收者,调用父类构造方法
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return //方法结束指令
LineNumberTable: //行号表 确定code数组的哪一部分对应于原始源文件中的给定行号。
line 5: 0
LocalVariableTable: //局部变量表
Start Length Slot Name Signature
0 5 0 this LByteCodeTest; // 作用范围0-4,变量槽下标0,变量名this,变量类型ByteCodeTest
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V //带参数方法描述符
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
Code:
//// 操作数栈深度,占用的Slot(变量槽)的大小(long,double占2个,其余1个),方法参数
stack=2, locals=1, args_size=1
//获取静态变量,入操作数栈
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
//加载常量,入操作数栈
3: ldc #3 // String Hello World!
// 调用虚方法
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 7: 0
line 8: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 args [Ljava/lang/String;
}
常用指令
加载和存储指令- _load_:将一个局部变量加载到操作数栈
- _store_:将一个常量加载到操作数栈
访问指令- 类字段 :getstatic、putstatic
- 成员变量:getfield、putfield
方法调用和返回指令- invokevirtual:用于调用对象的成员方法,根据对象的实际类型进行分派,支持多态。
- invokeinterface:用于调用接口方法,会在运行时搜索由特定对象实现的接口方法进行调用。
- invokespecial:用于调用一些需要特殊处理的方法,包括构造方法、私有方法和父类方法。
- invokestatic:用于调用静态方法。
- invokedynamic:用于在运行时动态解析出调用点限定符所引用的方法,并执行。
- 对象创建
- 创建实例或数组: new,newarray,new_
- 运算指令
- 对两个操作数栈的值进行某种运算,并把结果重新存储操作数栈
- 类型转换指令
- 操作数栈管理指令
- 栈顶一个或两个元素出栈 pop 或pop2
- 复制栈顶一个或两个数值并将复制值或者双份重新入栈; dup_
- 栈顶数值互换
- 控制转移指令
- 异常处理指令
- 同步指令
工具
参考
官网文档
带你看明白class二进制文件!
Class文件十六进制背后的秘密
Java字节码学习笔记(二):Java字节码怎么看