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) 编辑 收藏 举报