实战详解java反编译字节码(操作指令助记符)

4 篇文章 0 订阅

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;

/**
 * 字节码反编译测试.
 *
 * @author xindaqi
 * @since 2022-08-11 10:48
 */
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() {
        // (1)new:新建对象,在堆中为对象分配存储空间,并压入操作数栈顶
        // (2)dup:复制栈顶部一个字长内容,入栈(此时栈有两个相同地址)
        // (3)invkespecial:构造函数调用初始化方法<init>:()V,操作数栈顶弹出ByteCodeTest对象引用(dup)
        // (4)astore_1:从操作数栈顶取出ByteCodeTest对象存入局部变量1
        ByteCodeTest byteCodeTest = new ByteCodeTest();

        // (1)aload_1:从局部变量1装在引用类型ByteCodeTest
        // (2)invokespecial:调用当前类方法test()
        byteCodeTest.test();

        // (1)ldc:将常量池数据入栈
        // (2)astore_2:引用类型值String(包装类)存入局部变量2
        String var1 = "m";
        // iconst_1:int类型常量1入栈,istore_3:int类型值存入局部变量3
        int var2 = 1;

        // 同:新建对象四步
        UserEntity userEntity = new UserEntity();

        // (1)aload:从局部变量加载引用类型UserEntity
        // (2)invokevirtual:调用UserEntity类的方法getNickName()
        // (3)pop:弹出栈顶一个字长内容
        userEntity.getNickname();

        // 同:新建对象四步
        IUserService userService = new UserServiceImpl();
        // (1)aload:从局部变量加载引用类型IUserService
        // (2)invokeinterface:调用接口方法add()
        userService.add();


        // iconst_3:int类型常量3压入栈
        // anewarray:新建成员为引用类型的数组
        // 开始赋值:a、b、c,循环操作
        // (1)dup:复制栈顶一个字长内容
        // (2)iconst_0:int类型常量0压入栈
        // (3)ldc:常量池数据入栈
        // (4)aastore:引用类型值存入数组
        // invokestatic:调用静态方法:Stream.of()
        // invokestatic:调用静态方法:Collectors.toList()
        // invokeinterface:调用接口方法Stream.collect
        // checkcast:检查数据类型为给定类型:List
        // astore:存储引用类型局部变量
        List<String> var4 = Stream.of("a", "b", "c").collect(Collectors.toList());

        // aload:加载引用类型数据var4
        // invokedynamic:运行时解析,调用方法accept
        // invokeinterface:forEach为接口方法
        var4.forEach(s -> {
            if ("a".equals(s)) {
                // invokestatic:System中out为static方法
                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 常量入栈

序号指令描述
1aconst_nullnull对象压入栈
2iconst_m1int类型常量-1压入栈
3iconst_0int类型常量0入栈,其中,多个常量,叠加,其余同
4lconst_0long类型常量0入栈
5fconst_0float类型常量0入栈
6dconst_0double类型常量入栈
7bipush8位有符号整数入栈
8sipush16位有符号整数入栈
9ldc常量池中的数据入栈
10ldc_w常量池中的数据入栈(使用宽索引)
11ldc2_w常量池中long或double类型数据入栈

2.3.2 栈中值存储到局部变量

序号指令描述
1istoreint类型数据存入局部变量
2lstorelong类型数据存入局部变量
3fstorefloat类型数据存入局部变量
4dstoredouble类型数据存入局部变量
5astore引用类型或返回地址类型数据存入局部变量
6iastoreint类型数据存入数组
7lastorelong类型数据存入数组
8fastorefloat类型数据存入数组
9dastoredouble类型数据存入数组
10aastore引用类型或返回地址类型数据存入数组
11bastorebyte或boolean类型存入数组
12castorechar类型数据存入数组
13sastoreshort类型数据存入数组

2.3.3 从栈中加载数据

序号指令描述
1iload从局部变量中加载int类型数据
2lload从局部变量中加载long类型数据
3fload从局部变量中加载float类型数据
4dload从局部变量中加载double类型数据
5aload从局部变量中加载引用类型数据
6baload从数组加载byte或boolean类型数据
7caload从数组加载char类型数据
8saload从数组加载short类型数据
9aaload从数组加载引用类型数据
10faload从数组加载float数据
11iaload从数组加载int类型数据
12daload从数组加载double类型数据

2.3.4 通用栈操作

序号指令描述
1nop不操作
2pop弹出栈顶两个字长内容
3pop2复制栈顶部2个字长
4dup复制栈顶部一个字长
5dup_x1复制栈顶一个字长内容,然后将复制内容和原来弹出的2个字长内容入栈
6dup_x2复制栈顶一个字长内容,然后将复制内容和原来弹出的3个字长内容入栈
7dup2复制栈顶两个字长内容
8dup2_x1复制栈顶两个字长内容,然后将复制内容和原来弹出的三个字长内容入栈
9dup2_x2复制栈顶两个字长内容,然后将复制内容和原来弹出的四个字长内容入栈
10swap交换栈顶两个字长内容

2.3.5 对象操作

序号指令描述
1new新建对象
2checkcast检查对象是否为给定类型
3getfield获取对象中字段值
4putfield设置对象中字段值
5getstatic获取类中静态字段值
6putstatic设置类中静态字段值
7instanceof判断对象是否为给定类型

2.3.6 数组操作

序号指令描述
1newarray新建成员为基础类型的数组
2anewarray新建成员为引用类型的数组
3arraylength获取数组长度
4multianewarray分配新的多维数组

2.3.7 异常

序号指令描述
1athrow抛出异常或错误
2goto跳转,比如使用finally时,会有goto
3jsr跳转到子例程
4jsr_w跳转到子例程(宽索引)
5rct从子例程返回

2.3.8 方法调用

序号指令描述
1invokespecial:调用当前类方法
2invokevirtual调用引入类的方法
3invokeinterface调用接口方法
4invokestatic调用静态方法
5invokedynamic调用运行时解析的方法

2.3.9 方法返回

序号指令描述
1ireturn方法返回值为int或者boolean
2lreturn方法返回值为long
3freturn方法返回值为float
4dreturn方法返回值为double
5areturn方法返回值为引用类型
6return从方法中直接返回,void

2.3.10 线程同步

序号指令描述
1monitorenter进入并获取监视器
2monitorexit释放并退出对象监视器

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:调用运行时解析的方法。

  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天然玩家

坚持才能做到极致

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值