这是我参与11月更文挑战的第1天,活动详情查看:2021最后一次更文挑战
一、什么是字节码
Java bytecode 由单字节(byte)的指令组成,理论上最多支持 256 个操作码(opcode)。实际上 Java 只使用了200左右的操作码, 还有一些操作码则保留给调试操作。
根据指令的性质,主要分为四个大类:
- 1、栈操作指令,包括与局部变量交互的指令
- 2、程序流程控制指令
- 3、对象操作指令,包括方法调用指令
- 4、算术运算以及类型转换指令
1.1 通用栈操作指令如下
| 指令码 | 操作码(助记符) | 描述(栈指操作数栈) |
|---|---|---|
| 0x00 | nop | 空操作。 |
| 0x57 | pop | 从栈顶弹出一个字长的数据。 |
| 0x58 | pop2 | 从栈顶弹出两个字长的数据。 |
| 0x59 | dup | 复制栈顶一个字长的数据,将复制后的数据压栈。 |
| 0x5a | dup_x1 | 复制栈顶一个字长的数据,弹出栈顶两个字长数据,先将复制后的数据压栈,再将弹出的两个字长数据压栈。 |
| 0x5b | dup_x2 | 复制栈顶一个字长的数据,弹出栈顶三个字长的数据,将复制后的数据压栈,再将弹出的三个字长的数据压栈。 |
| 0x5c | dup2 | 复制栈顶两个字长的数据,将复制后的两个字长的数据压栈。 |
| 0x5d | dup2_x1 | 复制栈顶两个字长的数据,弹出栈顶三个字长的数据,将复制后的两个字长的数据压栈,再将弹出的三个字长的数据压栈。 |
| 0x5e | dup2_x2 | 复制栈顶两个字长的数据,弹出栈顶四个字长的数据,将复制后的两个字长的数据压栈,再将弹出的四个字长的数据压栈。 |
| 0x5f | swap | 交换栈顶两个字长的数据的位置。Java指令中没有提供以两个字长为单位的交换指令。 |
1.2 程序流程控制指令(部分指令)--条件跳转指令
| 指令码 | 操作码(助记符) | 描述(栈指操作数栈) |
|---|---|---|
| 0x99 | ifeq | 若栈顶int类型值为0则跳转。 |
| 0x9a | ifne | 若栈顶int类型值不为0则跳转。 |
| 0x9b | iflt | 若栈顶int类型值小于0则跳转。 |
| 0x9c | ifge | 若栈顶int类型值大于等于0则跳转。 |
| 0x9d | ifgt | 若栈顶int类型值大于0则跳转。 |
| 0x9e | ifle | 若栈顶int类型值小于等于0则跳转。 |
| 0x9f | if_icmpeq | 若栈顶两int类型值相等则跳转。 |
| 0xa0 | if_icmpne | 若栈顶两int类型值不相等则跳转。 |
1.3 对象操作指令
| 指令码 | 操作码(助记符) | 描述(栈指操作数栈) |
|---|---|---|
| 0xbb | new | 创建新的对象实例。 |
| 0xc0 | checkcast | 类型强转。 |
| 0xc1 | instanceof | 判断类型。 |
| 0xb4 | getfield | 获取对象字段的值。 |
| 0xb5 | putfield | 给对象字段赋值。 |
| 0xb2 | getstatic | 获取静态字段的值。 |
| 0xb3 | putstatic | 给静态字段赋值。 |
1.4 整型算术运算部分指令
| 指令码 | 操作码(助记符) | 描述(栈指操作数栈) |
|---|---|---|
| 0x60 | iadd | 将栈顶两int类型数相加,结果入栈。 |
| 0x64 | isub | 将栈顶两int类型数相减,结果入栈。 |
| 0x68 | imul | 将栈顶两int类型数相乘,结果入栈。 |
| 0x6c | idiv | 将栈顶两int类型数相除,结果入栈。 |
| 0x70 | irem | 将栈顶两int类型数取模,结果入栈。 |
| 0x74 | ineg | 将栈顶int类型值取负,结果入栈。 |
二、案例
2.1 简单字节码案例分析
1、源码
javac ByteCode.java //用jdk自带的工具javac 编译源代码
public class ByteCode {
public static void main(String[] args) {
ByteCode obj = new ByteCode();
}
}
2、查看字节码
javap的用法格式如下:javap <options> <classes> javap 参数如下:
- -help --help -? 输出此用法消息
- -version 版本信息,其实是当前javap所在jdk的版本信息,不是class在哪个jdk下生成的。
- -v -verbose 输出附加信息(包括行号、本地变量表,反汇编等详细信息)
- -l 输出行号和本地变量表
- -public 仅显示公共类和成员
- -protected 显示受保护的/公共类和成员
- -package 显示程序包/受保护的/公共类 和成员 (默认)
- -p -private 显示所有类和成员
- -c 对代码进行反汇编
- -s 输出内部类型签名
- -sysinfo 显示正在处理的类的系统信息 (路径, 大小, 日期, MD5 散列)
- -constants 显示静态最终常量
- -classpath 指定查找用户类文件的位置
- -bootclasspath 覆盖引导类文件的位置
javap -c ByteCode //用jdk自带的工具javap 反编译字节码
public class jvm.week1.ByteCode {
public jvm.week1.ByteCode();
Code:
0: aload_0 //将局部变量0加载到操作数栈
1: invokespecial #1 // Method java/lang/Object."<init>":()V 调用超类构造方法,实例初始化方法,私有方法
4: return //void函数返回
public static void main(java.lang.String[]);
Code:
0: new #2 // class jvm/week1/ByteCode 创建一个对象,并将其引用值压入栈顶
3: dup // 复制栈顶数值并将复制值压入栈顶
4: invokespecial #3 // Method "<init>":()V 调用超类构造方法,实例初始化方法,私有方法
7: astore_1 //将栈顶引用型数值存入第二个本地变量
8: return
}