• 实战详解java程序反编译映射的字节码(助记符)


    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:构造函数调用初始化方法:()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) {
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85

    2.2 反编译获取汇编指令

    进入对应的字节码路径:D:\java-basic-with-maven\target\classes\com\monkey\java_study\clzz
    执行反编译字节码生成汇编指令:

    javap -c ByteCodeTest.class
    
    • 1

    控制台生成的反编译汇编指令如下:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    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."":()V         
           4: return                                                                            
                                                                                                
      public void call();                                                                       
        Code:                                                                                   
           0: new           #5                  // class com/monkey/java_study/clzz/ByteCodeTest
           3: dup
           4: invokespecial #6                  // Method "":()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."":()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."":()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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64

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

  • 相关阅读:
    【每日一题Day37】LC795区间子数组的个数 | 单调栈 模拟
    MBR分区表的简介
    MAC安装stable diffusion
    国内乳品行业数据浅析
    Penpad获Gate Labs以及Scroll联创Sandy的投资
    分享119个ASP.NET源码总有一个是你想要的
    还摆个屁的烂?用Python画如此漂亮的专业插图 ?简直So easy!
    【软件与系统安全笔记】五、内存破坏防御
    鸿蒙DevEco Studio 4.1 Release-模拟器启动方式错误
    ​怎么测试websocket接口
  • 原文地址:https://blog.csdn.net/Xin_101/article/details/126288966