1 缘起
刚开始学习Java时,只知道Java程序需要编译成字节码,交给JVM执行(这里不讨论编译和解释执行),
以践行一次编译到处运行的伟大设计理念,
并不知道字节码长什么样,随着学习的深入,发现可以通过反编译的方式,
观察Java程序与字节码的映射关系,以更加深度了解Java程序的运作,
Java程序对于开发者是可读的,
字节码对于JVM是可读的,
二进制对于处理器是可读的,
不同的角色处理不同层面的计算机语言。
比如,java8中try…with…resource,通过字节码反编译可清晰地看到,确实有close操作,
所有,学习字节码相关知识,
现以实战方式分享Java字节码反编译,帮助读者进一步理解Java程序设计。
2 Code实践
2.1 源码
测试源码如下,
使用IDEA集成开发环境编译源码,生成的字节码保存在target文件夹,
以实例的方式讲解字节码反编译与Java程序对应关系,
为方便理解和记忆,我在源码中给出了每条语句对应的字节码反编译助记符,
由于源码会自动折叠,所以先给出截图,后面有完整的源码,
2.1.1 新建对象new
2.1.2 调用方法
2.1.3 局部变量赋值
2.1.4 新建引入的对象及调用方法
2.1.5 新建接口对象及调用方法
2.1.6 调用运行时解析方法
源码如下:
package com.monkey.java_study.clzz;
import com.monkey.java_study.common.entity.UserEntity;
import com.monkey.java_study.proxy.jdk_proxy.IUserService;
import com.monkey.java_study.proxy.jdk_proxy.impl.UserServiceImpl;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class ByteCodeTest {
public static final String TEST_NAME = "hello world!";
private void test() {
System.out.println(">>>>>>>>I am test method in current class");
}
public void call() {
ByteCodeTest byteCodeTest = new ByteCodeTest();
byteCodeTest.test();
String var1 = "m";
int var2 = 1;
UserEntity userEntity = new UserEntity();
userEntity.getNickname();
IUserService userService = new UserServiceImpl();
userService.add();
List<String> var4 = Stream.of("a", "b", "c").collect(Collectors.toList());
var4.forEach(s -> {
if ("a".equals(s)) {
System.out.println(">>>>>>>>I am " + s);
}
});
}
public static void main(String[] args) {
}
}
2.2 反编译获取汇编指令
进入对应的字节码路径:D:\java-basic-with-maven\target\classes\com\monkey\java_study\clzz
执行反编译字节码生成汇编指令:
javap -c ByteCodeTest.class
控制台生成的反编译汇编指令如下:
PS D:\java-basic-with-maven\target\classes\com\monkey\java_study\clzz> javap -c .\ByteCodeTest.class
Compiled from "ByteCodeTest.java"
public class com.monkey.java_study.clzz.ByteCodeTest {
public static final java.lang.String TEST_NAME;
public com.monkey.java_study.clzz.ByteCodeTest();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public void call();
Code:
0: new #5 // class com/monkey/java_study/clzz/ByteCodeTest
3: dup
4: invokespecial #6 // Method "<init>":()V
7: astore_1
8: aload_1
9: invokespecial #7 // Method test:()V
12: ldc #8 // String m
14: astore_2
15: iconst_1
16: istore_3
17: new #9 // class com/monkey/java_study/common/entity/UserEntity
20: dup
21: invokespecial #10 // Method com/monkey/java_study/common/entity/UserEntity."<init>":()V
24: astore 4
26: aload 4
28: invokevirtual #11 // Method com/monkey/java_study/common/entity/UserEntity.getNickname:()Ljava/lang/String;
31: pop
32: new #12 // class com/monkey/java_study/proxy/jdk_proxy/impl/UserServiceImpl
35: dup
36: invokespecial #13 // Method com/monkey/java_study/proxy/jdk_proxy/impl/UserServiceImpl."<init>":()V
39: astore 5
41: aload 5
43: invokeinterface #14, 1 // InterfaceMethod com/monkey/java_study/proxy/jdk_proxy/IUserService.add:()V
48: iconst_3
49: anewarray #15 // class java/lang/String
52: dup
53: iconst_0
54: ldc #16 // String a
56: aastore
57: dup
58: iconst_1
59: ldc #17 // String b
61: aastore
62: dup
63: iconst_2
64: ldc #18 // String c
66: aastore
67: invokestatic #19 // InterfaceMethod java/util/stream/Stream.of:([Ljava/lang/Object;)Ljava/util/stream/Stream;
70: invokestatic #20 // Method java/util/stream/Collectors.toList:()Ljava/util/stream/Collector;
73: invokeinterface #21, 2 // InterfaceMethod java/util/stream/Stream.collect:(Ljava/util/stream/Collector;)Ljava/lang/Object;
78: checkcast #22 // class java/util/List
81: astore 6
83: aload 6
85: invokedynamic #23, 0 // InvokeDynamic #0:accept:()Ljava/util/function/Consumer;
90: invokeinterface #24, 2 // InterfaceMethod java/util/List.forEach:(Ljava/util/function/Consumer;)V
95: return
public static void main(java.lang.String[]);
Code:
0: return
}
2.3 常用字节码解析
2.3.1 常量入栈
序号 | 指令 | 描述 |
---|
1 | aconst_null | null对象压入栈 |
2 | iconst_m1 | int类型常量-1压入栈 |
3 | iconst_0 | int类型常量0入栈,其中,多个常量,叠加,其余同 |
4 | lconst_0 | long类型常量0入栈 |
5 | fconst_0 | float类型常量0入栈 |
6 | dconst_0 | double类型常量入栈 |
7 | bipush | 8位有符号整数入栈 |
8 | sipush | 16位有符号整数入栈 |
9 | ldc | 常量池中的数据入栈 |
10 | ldc_w | 常量池中的数据入栈(使用宽索引) |
11 | ldc2_w | 常量池中long或double类型数据入栈 |
2.3.2 栈中值存储到局部变量
序号 | 指令 | 描述 |
---|
1 | istore | int类型数据存入局部变量 |
2 | lstore | long类型数据存入局部变量 |
3 | fstore | float类型数据存入局部变量 |
4 | dstore | double类型数据存入局部变量 |
5 | astore | 引用类型或返回地址类型数据存入局部变量 |
6 | iastore | int类型数据存入数组 |
7 | lastore | long类型数据存入数组 |
8 | fastore | float类型数据存入数组 |
9 | dastore | double类型数据存入数组 |
10 | aastore | 引用类型或返回地址类型数据存入数组 |
11 | bastore | byte或boolean类型存入数组 |
12 | castore | char类型数据存入数组 |
13 | sastore | short类型数据存入数组 |
2.3.3 从栈中加载数据
序号 | 指令 | 描述 |
---|
1 | iload | 从局部变量中加载int类型数据 |
2 | lload | 从局部变量中加载long类型数据 |
3 | fload | 从局部变量中加载float类型数据 |
4 | dload | 从局部变量中加载double类型数据 |
5 | aload | 从局部变量中加载引用类型数据 |
6 | baload | 从数组加载byte或boolean类型数据 |
7 | caload | 从数组加载char类型数据 |
8 | saload | 从数组加载short类型数据 |
9 | aaload | 从数组加载引用类型数据 |
10 | faload | 从数组加载float数据 |
11 | iaload | 从数组加载int类型数据 |
12 | daload | 从数组加载double类型数据 |
2.3.4 通用栈操作
序号 | 指令 | 描述 |
---|
1 | nop | 不操作 |
2 | pop | 弹出栈顶两个字长内容 |
3 | pop2 | 复制栈顶部2个字长 |
4 | dup | 复制栈顶部一个字长 |
5 | dup_x1 | 复制栈顶一个字长内容,然后将复制内容和原来弹出的2个字长内容入栈 |
6 | dup_x2 | 复制栈顶一个字长内容,然后将复制内容和原来弹出的3个字长内容入栈 |
7 | dup2 | 复制栈顶两个字长内容 |
8 | dup2_x1 | 复制栈顶两个字长内容,然后将复制内容和原来弹出的三个字长内容入栈 |
9 | dup2_x2 | 复制栈顶两个字长内容,然后将复制内容和原来弹出的四个字长内容入栈 |
10 | swap | 交换栈顶两个字长内容 |
2.3.5 对象操作
序号 | 指令 | 描述 |
---|
1 | new | 新建对象 |
2 | checkcast | 检查对象是否为给定类型 |
3 | getfield | 获取对象中字段值 |
4 | putfield | 设置对象中字段值 |
5 | getstatic | 获取类中静态字段值 |
6 | putstatic | 设置类中静态字段值 |
7 | instanceof | 判断对象是否为给定类型 |
2.3.6 数组操作
序号 | 指令 | 描述 |
---|
1 | newarray | 新建成员为基础类型的数组 |
2 | anewarray | 新建成员为引用类型的数组 |
3 | arraylength | 获取数组长度 |
4 | multianewarray | 分配新的多维数组 |
2.3.7 异常
序号 | 指令 | 描述 |
---|
1 | athrow | 抛出异常或错误 |
2 | goto | 跳转,比如使用finally时,会有goto |
3 | jsr | 跳转到子例程 |
4 | jsr_w | 跳转到子例程(宽索引) |
5 | rct | 从子例程返回 |
2.3.8 方法调用
序号 | 指令 | 描述 |
---|
1 | invokespecial:调用当前类方法 | |
2 | invokevirtual | 调用引入类的方法 |
3 | invokeinterface | 调用接口方法 |
4 | invokestatic | 调用静态方法 |
5 | invokedynamic | 调用运行时解析的方法 |
2.3.9 方法返回
序号 | 指令 | 描述 |
---|
1 | ireturn | 方法返回值为int或者boolean |
2 | lreturn | 方法返回值为long |
3 | freturn | 方法返回值为float |
4 | dreturn | 方法返回值为double |
5 | areturn | 方法返回值为引用类型 |
6 | return | 从方法中直接返回,void |
2.3.10 线程同步
序号 | 指令 | 描述 |
---|
1 | monitorenter | 进入并获取监视器 |
2 | monitorexit | 释放并退出对象监视器 |
3 小结
新建对象分为4步:
(1)new:新建对象,在堆中为对象分配存储空间,并压入操作数栈顶;
(2)dup:复制栈顶部一个字长内容,入栈(此时栈有两个相同地址);
(3)invkespecial:构造函数调用初始化方法😦)V,操作数栈顶弹出ByteCodeTest对象引用(dup);
(4)astore_1:从操作数栈顶取出ByteCodeTest对象存入局部变量1。
方法调用有5种方式:
(1)invokespecial:调用当前类方法;
(2)invokevirtual:调用引入类的方法;
(3)invokeinterface:调用接口方法;
(4)invokestatic:调用静态方法;
(5)invokedynamic:调用运行时解析的方法。