Java字节码分析

Java字节码分析


对于源码的效率,但从源码来看有时无法分析出准确的结果,因为不同的编译器版本可能会将相同的源码编译成不同的字节码,Java真正执行的也是字节码,所以要分析源码的性能需要从字节码的角度分析。

查看字节码详细内容 javap

javap
查看classFile的命令并将输出到file.txt

javap -v classFile > file.txt

参数 解释
‐version 版本信息
‐v ‐verbose 输出附加信息
‐l 输出行号和本地变量表
‐public 仅显示公共类和成员
‐protected 显示受保护的/公共类和成员
‐package 显示程序包/受保护的/公共类和成员 (默认)
‐p ‐private 显示所有类和成员
‐c 对代码进行反汇编
‐s 输出内部类型签名
‐sysinfo 显示正在处理的类的系统信息 (路径, 大小, 日期, MD5 散列)
‐constants 显示最终常量
‐classpath 指定查找用户类文件的位置
‐cp 指定查找用户类文件的位置
‐bootclasspath 覆盖引导类文件的位置
常量池描述符
Constant Type Value 说明
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 表示一个动态方法调用点
字段描述符
FieldType term Type Interpretation
B byte signed byte
C char Unicode character code point in the BasicMultilingual Plane, encoded with UTF-16
D double double-precision floating-point value
F float single-precision floating-point value
I int integer
J long long integer
LClassName; reference an instance of class ClassName
S short signed short
Z boolean true or false
[ reference one array dimension
方法描述符

方法:

Object m(int i, double d, Thread t)

--->描述符

(IDLjava/lang/Thread;)Ljava/lang/Object;

解释:
传入(I 第一个参数int类型 D第二个参数double L第三个参数一个对象,后面是对象的描述java/lang/Thread;)输出Ljava/lang/Object;一个object对象

package JavaCore.JVM.ByteCode;

/*******************************************************************************
 * @Copyright (C), 2018-2019,github:Swagger-Ranger 
 * @FileName: ByteCode_Test
 * @Author: liufei32@outlook.com
 * @Date: 2019/4/13 14:51
 * @Description:
 * @Aha-eureka:
 *
 * javap生成详细的命令
 * javap -v ByteCode_Test.class > ByteCode_Test.txt
 * 内容:
 * 第一部分:显示了生成这个class的java源文件、版本信息、生成时间等。
 * 第二部分:显示了该类中所涉及到常量池,共35个常量。
 * 第三部分:显示该类的构造器,编译器自动插入的。
 * 第四部分:显示了main方的信息。(这个是需要我们重点关注的)
 *
 * //第一部分
 * Classfile /D:/Swagger-Ranger/git-workspace/Algorithms/out/production/Algorithms/JavaCore/JVM/ByteCode/ByteCode_Test.class
 *   Last modified 2019-4-13; size 617 bytes
 *   MD5 checksum 646edba623f52c83adb9e067841a1ffb
 *   Compiled from "ByteCode_Test.java"
 * public class JavaCore.JVM.ByteCode.ByteCode_Test
 *   minor version: 0
 *   major version: 52
 *   flags: ACC_PUBLIC, ACC_SUPER
 *
 * //第二部分
 * Constant pool:
 *
 *   //这里在描述常量池时,所有的utf-8类型的都是值,即描述符的内容,而常量描述符的内容则用utf-8对于的常量序号引用来描述,并使用.:
 *   //等符号来拼接。比如:#1 = Methodref   #5.#23  --层层引用还原#5.#23即为-->Class:java/lang/Object."<init>"()V返回void---对没错就是后面注释的内容
 *
 *    //常量的序号和类型       常量描述(使用字段描述符或方法描述符描述)   注释
 *
 *    #1 = Methodref          #5.#23         // java/lang/Object."<init>":()V
 *    #2 = Fieldref           #24.#25        // java/lang/System.out:Ljava/io/PrintStream;
 *    #3 = Methodref          #26.#27        // java/io/PrintStream.println:(I)V
 *    #4 = Class              #28            // JavaCore/JVM/ByteCode/ByteCode_Test
 *    #5 = Class              #29            // java/lang/Object
 *    #6 = Utf8               <init>
 *    #7 = Utf8               ()V
 *    #8 = Utf8               Code
 *    #9 = Utf8               LineNumberTable
 *   #10 = Utf8               LocalVariableTable
 *   #11 = Utf8               this
 *   #12 = Utf8               LJavaCore/JVM/ByteCode/ByteCode_Test;
 *   #13 = Utf8               main
 *   #14 = Utf8               ([Ljava/lang/String;)V
 *   #15 = Utf8               args
 *   #16 = Utf8               [Ljava/lang/String;
 *   #17 = Utf8               a
 *   #18 = Utf8               I
 *   #19 = Utf8               b
 *   #20 = Utf8               c
 *   #21 = Utf8               SourceFile
 *   #22 = Utf8               ByteCode_Test.java
 *   #23 = NameAndType        #6:#7          // "<init>":()V
 *   #24 = Class              #30            // java/lang/System
 *   #25 = NameAndType        #31:#32        // out:Ljava/io/PrintStream;
 *   #26 = Class              #33            // java/io/PrintStream
 *   #27 = NameAndType        #34:#35        // println:(I)V
 *   #28 = Utf8               JavaCore/JVM/ByteCode/ByteCode_Test
 *   #29 = Utf8               java/lang/Object
 *   #30 = Utf8               java/lang/System
 *   #31 = Utf8               out
 *   #32 = Utf8               Ljava/io/PrintStream;
 *   #33 = Utf8               java/io/PrintStream
 *   #34 = Utf8               println
 *   #35 = Utf8               (I)V
 *
 *   //第三部分该类的构造器,编译器自动插入的。
 * {
 *   public JavaCore.JVM.ByteCode.ByteCode_Test();
 *     descriptor: ()V                           //构造函数描述,()V-无传入参数并返回空
 *     flags: ACC_PUBLIC
 *     Code:
 *       stack=1, locals=1, args_size=1
 *          0: aload_0
 *          1: invokespecial #1                  // Method java/lang/Object."<init>":()V
 *          4: return
 *       LineNumberTable:
 *         line 12: 0
 *       LocalVariableTable:
 *         Start  Length  Slot  Name   Signature
 *             0       5     0  this   LJavaCore/JVM/ByteCode/ByteCode_Test;
 *
 * //第四部分 main方的信息。(这个是需要我们重点关注的)
 *   public static void main(java.lang.String[]);
 *     descriptor: ([Ljava/lang/String;)V             //方法描述,([Ljava/lang/String;)传入一个string一维数组参数;V-返回空
 *     flags: ACC_PUBLIC, ACC_STATIC                  //方法修饰符:ACC_PUBLIC :public, ACC_STATIC :static
 *     Code:                                          //代码块
 *       stack=2, locals=4, args_size=1               //首先对Code作了统计,stack操作栈(任何操作都先要把值放入操作栈才能操作)有2个,locals本地变量有4个,args_size参数个数有1个
 *          0: iconst_2          //将数字2值压入操作栈,位于栈的最上面
 *          1: istore_1         //从操作栈中弹出一个元素(数字2),放入到本地变量表中,位于下标为1的位置(下标为0的是this)
 *          2: iconst_3        //将数字5值压入操作栈,位于栈的最上面
 *          3: istore_2        //从操作栈中弹出一个元素(5),放入到本地变量表中,位于第下标为2个位置
 *          4: iload_2         //将本地变量表中下标为2的位置元素压入操作栈(5)
 *          5: iload_1         //将本地变量表中下标为1的位置元素压入操作栈(2)
 *          6: isub            //操作栈中的2个数字相减
 *          7: istore_3        // 将相减的结果压入到本地本地变量表中,位于下标为3的位置
 *          // 开始执行打印语句,那首先要找到打印的内容,通过getstatic #2找到对应的常量即常量池中的#2常量,即可找到对应的引用
 *          8: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
 *         11: iload_3                //将本地变量表中下标为3的位置元素压入操作栈(3)
 *         // 通过#3号找到对应的常量,然后invokevirtual去执行#3= Methodref 方法引用,即可找到对应的引用,进行方法调用
 *         12: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
 *         15: return              //返回
 *       LineNumberTable:          //这里是源码行号和字节码步骤作一一对应,当然因为我将注释复制了过来所以这里源码行号有改变
 *         line 15: 0
 *         line 16: 2
 *         line 17: 4
 *         line 18: 8
 *         line 19: 15
 *       LocalVariableTable:           //本地变量表
 *                        槽位  变量名  字段描述符
 *         Start  Length  Slot  Name   Signature
 *             0      16     0  args   [Ljava/lang/String;
 *             2      14     1     a   I
 *             4      12     2     b   I
 *             8       8     3     c   I
 * }
 * SourceFile: "ByteCode_Test.java"
 *******************************************************************************/

public class ByteCode_Test {

    public static void main( String[] args ) {
        int a = 2;
        int b = 3;
        int c = b - a;
        System.out.println(c);
    }
}

实例分析

i++与++i

package JavaCore.JVM.ByteCode;

/*******************************************************************************
 * @Copyright (C), 2018-2019,github:Swagger-Ranger 
 * @FileName: ByteCode_iplusplus_plusplusi
 * @Author: liufei32@outlook.com
 * @Date: 2019/4/14 0:04
 * @Description: i++和++i的具体字节码
 * @Aha-eureka:
 *******************************************************************************/

public class ByteCode_iplusplus_plusplusi {

    public void method1() {
        int i = 5;
        int a = i++;
        System.out.println(a);
    }

    public void method2() {
        int i = 5;
        int a = ++i;
        System.out.println(a);
    }

    public static void main( String[] args ) {
        new ByteCode_iplusplus_plusplusi().method1();
        new ByteCode_iplusplus_plusplusi().method2();
    }
}

/**
 * 命令:javap -v ByteCode_iplusplus_plusplusi.class > ByteCode_iplusplus_plusplusi.txt
 *
 * Classfile /D:/Swagger-Ranger/git-workspace/Algorithms/out/production/Algorithms/JavaCore/JVM/ByteCode/ByteCode_iplusplus_plusplusi.class
 *   Last modified 2019-4-14; size 876 bytes
 *   MD5 checksum 6619df3d2429d5c853b4d1972b1e6504
 *   Compiled from "ByteCode_iplusplus_plusplusi.java"
 * public class JavaCore.JVM.ByteCode.ByteCode_iplusplus_plusplusi
 *   minor version: 0
 *   major version: 52
 *   flags: ACC_PUBLIC, ACC_SUPER
 * Constant pool:
 *    #1 = Methodref          #8.#27         // java/lang/Object."<init>":()V
 *    #2 = Fieldref           #28.#29        // java/lang/System.out:Ljava/io/PrintStream;
 *    #3 = Methodref          #30.#31        // java/io/PrintStream.println:(I)V
 *    #4 = Class              #32            // JavaCore/JVM/ByteCode/ByteCode_iplusplus_plusplusi
 *    #5 = Methodref          #4.#27         // JavaCore/JVM/ByteCode/ByteCode_iplusplus_plusplusi."<init>":()V
 *    #6 = Methodref          #4.#33         // JavaCore/JVM/ByteCode/ByteCode_iplusplus_plusplusi.method1:()V
 *    #7 = Methodref          #4.#34         // JavaCore/JVM/ByteCode/ByteCode_iplusplus_plusplusi.method2:()V
 *    #8 = Class              #35            // java/lang/Object
 *    #9 = Utf8               <init>
 *   #10 = Utf8               ()V
 *   #11 = Utf8               Code
 *   #12 = Utf8               LineNumberTable
 *   #13 = Utf8               LocalVariableTable
 *   #14 = Utf8               this
 *   #15 = Utf8               LJavaCore/JVM/ByteCode/ByteCode_iplusplus_plusplusi;
 *   #16 = Utf8               method1
 *   #17 = Utf8               i
 *   #18 = Utf8               I
 *   #19 = Utf8               a
 *   #20 = Utf8               method2
 *   #21 = Utf8               main
 *   #22 = Utf8               ([Ljava/lang/String;)V
 *   #23 = Utf8               args
 *   #24 = Utf8               [Ljava/lang/String;
 *   #25 = Utf8               SourceFile
 *   #26 = Utf8               ByteCode_iplusplus_plusplusi.java
 *   #27 = NameAndType        #9:#10         // "<init>":()V
 *   #28 = Class              #36            // java/lang/System
 *   #29 = NameAndType        #37:#38        // out:Ljava/io/PrintStream;
 *   #30 = Class              #39            // java/io/PrintStream
 *   #31 = NameAndType        #40:#41        // println:(I)V
 *   #32 = Utf8               JavaCore/JVM/ByteCode/ByteCode_iplusplus_plusplusi
 *   #33 = NameAndType        #16:#10        // method1:()V
 *   #34 = NameAndType        #20:#10        // method2:()V
 *   #35 = Utf8               java/lang/Object
 *   #36 = Utf8               java/lang/System
 *   #37 = Utf8               out
 *   #38 = Utf8               Ljava/io/PrintStream;
 *   #39 = Utf8               java/io/PrintStream
 *   #40 = Utf8               println
 *   #41 = Utf8               (I)V
 * {
 *   public JavaCore.JVM.ByteCode.ByteCode_iplusplus_plusplusi();
 *     descriptor: ()V
 *     flags: ACC_PUBLIC
 *     Code:
 *       stack=1, locals=1, args_size=1
 *          0: aload_0
 *          1: invokespecial #1                  // Method java/lang/Object."<init>":()V
 *          4: return
 *       LineNumberTable:
 *         line 12: 0
 *       LocalVariableTable:
 *         Start  Length  Slot  Name   Signature
 *             0       5     0  this   LJavaCore/JVM/ByteCode/ByteCode_iplusplus_plusplusi;
 *
 *   public void method1();   //i++
 *     descriptor: ()V
 *     flags: ACC_PUBLIC
 *     Code:
 *       stack=2, locals=3, args_size=1
 *          0: iconst_5       //将5压入操作栈
 *          1: istore_1       //从操作栈中弹出变量并保存到下标为1的本地变量表
 *          2: iload_1        //加载下标为1的本地变量表中的变量到操作栈
 *          3: iinc          1, 1    //将本地变量表中下标为1的变量加1,这句命令iinc直接操作本地变量表并跟了两个参数1,1
 *          6: istore_2       //将操作栈中的变量(值为1)弹出并保存到下标为2的本地变量表
 *          7: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
 *         10: iload_2        //将本地变量表中下标为2的变量加载到操作栈
 *         11: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V  //这里打印传参I就是操作栈中的变量i(值为1)
 *         14: return
 *       LineNumberTable:
 *         line 15: 0
 *         line 16: 2
 *         line 17: 7
 *         line 18: 14
 *       LocalVariableTable:
 *         Start  Length  Slot  Name   Signature
 *             0      15     0  this   LJavaCore/JVM/ByteCode/ByteCode_iplusplus_plusplusi;
 *             2      13     1     i   I
 *             7       8     2     a   I
 *
 *   public void method2();  //++i
 *     descriptor: ()V
 *     flags: ACC_PUBLIC
 *     Code:
 *       stack=2, locals=3, args_size=1
 *          0: iconst_5              //将5压入操作栈
 *          1: istore_1              //从操作栈中弹出变量并保存到下标为1的本地变量表
 *          2: iinc          1, 1    //将本地变量表中下标为1的变量加1,这句命令iinc直接操作本地变量表并跟了两个参数1,1
 *          5: iload_1               //加载下标为1的本地变量表中的变量到操作栈
 *          6: istore_2
 *          7: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
 *         10: iload_2
 *         11: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
 *         14: return
 *       LineNumberTable:
 *         line 21: 0
 *         line 22: 2
 *         line 23: 7
 *         line 24: 14
 *       LocalVariableTable:
 *         Start  Length  Slot  Name   Signature
 *             0      15     0  this   LJavaCore/JVM/ByteCode/ByteCode_iplusplus_plusplusi;
 *             2      13     1     i   I
 *             7       8     2     a   I
 *
 *   public static void main(java.lang.String[]);
 *     descriptor: ([Ljava/lang/String;)V
 *     flags: ACC_PUBLIC, ACC_STATIC
 *     Code:
 *       stack=2, locals=1, args_size=1
 *          0: new           #4                  // class JavaCore/JVM/ByteCode/ByteCode_iplusplus_plusplusi
 *          3: dup
 *          4: invokespecial #5                  // Method "<init>":()V
 *          7: invokevirtual #6                  // Method method1:()V
 *         10: new           #4                  // class JavaCore/JVM/ByteCode/ByteCode_iplusplus_plusplusi
 *         13: dup
 *         14: invokespecial #5                  // Method "<init>":()V
 *         17: invokevirtual #7                  // Method method2:()V
 *         20: return
 *       LineNumberTable:
 *         line 27: 0
 *         line 28: 10
 *         line 29: 20
 *       LocalVariableTable:
 *         Start  Length  Slot  Name   Signature
 *             0      21     0  args   [Ljava/lang/String;
 * }
 * SourceFile: "ByteCode_iplusplus_plusplusi.java"
 */

区别:

  • i++
    只是在本地变量中对数字做了相加,并没有将数据压入到操作栈 将前面拿到的数字1,
    再次从操作栈中拿到,压入到本地变量中
  • ++i
    将本地变量中的数字做了相加,并且将数据压入到操作栈 将操作栈中的数据,
    再次压入到本地变量中

本博客为Swagger-Ranger的笔记分享,文章会持续更新
文中源码地址: https://github.com/Swagger-Ranger
欢迎交流指正,如有侵权请联系作者确认删除: liufei32@outlook.com

posted on 2019-04-14 21:43  Swagger-Ranger  阅读(210)  评论(0编辑  收藏  举报

导航