[JVM] 用Javap分析字节码文件
Java源文件经过编译器编译会生成 .class文件(字节码),然后才能在JVM上运行。
javap是JDK自带的分析字节码文件的工具,它的用法如下。
F:\IdeaProject\HappyJava\src>javap -help 用法: javap <options> <classes> 其中, 可能的选项包括: -help --help -? 输出此用法消息 -version 版本信息 -v -verbose 输出附加信息 -l 输出行号和本地变量表 -public 仅显示公共类和成员 -protected 显示受保护的/公共类和成员 -package 显示程序包/受保护的/公共类 和成员 (默认) -p -private 显示所有类和成员 -c 对代码进行反汇编 -s 输出内部类型签名 -sysinfo 显示正在处理的类的 系统信息 (路径, 大小, 日期, MD5 散列) -constants 显示最终常量 --module <模块>, -m <模块> 指定包含要反汇编的类的模块 --module-path <路径> 指定查找应用程序模块的位置 --system <jdk> 指定查找系统模块的位置 --class-path <路径> 指定查找用户类文件的位置 -classpath <路径> 指定查找用户类文件的位置 -cp <路径> 指定查找用户类文件的位置 -bootclasspath <路径> 覆盖引导类文件的位置
以一个普通的Java类为例:
1 public class ClassStructure 2 { 3 private static final double _e = 2.73; 4 5 private String name; 6 public int age; 7 public String[] hobbies; 8 9 public static void sayHi() 10 { 11 System.out.println("Hi, friend!"); 12 } 13 14 public void printNum() 15 { 16 for (int i = 0; i < 10; i++) { 17 for (int j = 0; j < 15; j++) { 18 System.out.println("xyz"); 19 } 20 } 21 } 22 }
首先,将其编译为Class文件,-g:vars 的作用是将方法内的局部变量表的名称也生成到Class文件中。否则IDE会用arg0, arg1之类的占位符代替原有的参数名,不便于调试。
F:\IdeaProject\HappyJava\src>javac -g:vars jvm/classStructure/ClassStructure.java
接下来,用 javap 分析字节码,它的主要几个用法为:
1. -v 或者 -verbose 输出附加信息,包括常量池,反编译的信息等,涵盖了大部分信息。
2. -c 输出反编译的结果
3. -p 或者 -private 显示所有类和成员(包括 private 修饰的)
先用 -p ,得到所有公有私有成员:
F:\IdeaProject\HappyJava\src>javap -p jvm.classStructure.ClassStructure public class jvm.classStructure.ClassStructure { private static final double _e; private java.lang.String name; public int age; public java.lang.String[] hobbies; public jvm.classStructure.ClassStructure(); public static void sayHi(); public void printNum(); }
再用 -c 反汇编,关键是看懂各个助记符的意思,跟x86汇编语言意思差不多,内容太多,可以看官网文档或者《深入理解Java虚拟机》末尾的附录。比如:
aload_0: 是把第1个引用类型本地变量推送至栈顶。这里是对象自身的引用this。
invokespecia: 调用超类构造方法/实例初始化方法/私有方法。这里调用超类Object的init(),因为没有覆盖init()方法。
invokevirtual: 调用实例方法,最常见的一种。/
getstatic: 获取指定类的静态域,并将其值压入栈顶。这里是System.out。
ldc: 将int,float,String型常量从常量值中推送至栈顶。
bipush n:将单字节常量值n(-128~127)推送至栈顶。bipush 10; bipush 15是因为 i 和 j 分别循环10次和15次。
if_icmpge row_num: if greater or equal than 0, then jump to Row row_num.
iinc: 自增操作。
goto: 跳转。
语句旁边的#1,#2等各自对应的是什么在常量池里(Constant pool),用 -v 可以查看到。
F:\IdeaProject\HappyJava\src>javap -c jvm.classStructure.ClassStructure public class jvm.classStructure.ClassStructure { public int age; public java.lang.String[] hobbies; public jvm.classStructure.ClassStructure(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public static void sayHi(); Code: 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #3 // String Hi, friend! 5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return public void printNum(); Code: 0: iconst_0 1: istore_1 2: iload_1 3: bipush 10 5: if_icmpge 36 8: iconst_0 9: istore_2 10: iload_2 11: bipush 15 13: if_icmpge 30 16: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 19: ldc #5 // String xyz 21: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 24: iinc 2, 1 27: goto 10 30: iinc 1, 1 33: goto 2 36: return }
如果用 -v 可以获得基本所有信息,开头是常量池,然后是属性和方法。每行常量最前面都是编号(#1,#2等),之后的方法里引用这些常量的时候就用编号。
F:\IdeaProject\HappyJava\src>javap -v -p jvm.classStructure.ClassStructure Classfile /F:/IdeaProject/HappyJava/src/jvm/classStructure/ClassStructure.class Last modified 2019年1月23日; size 734 bytes MD5 checksum 9c618383b59ffc86b1d830b09d4f0788 public class jvm.classStructure.ClassStructure minor version: 0 major version: 54 flags: (0x0021) ACC_PUBLIC, ACC_SUPER this_class: #6 // jvm/classStructure/ClassStructure super_class: #7 // java/lang/Object interfaces: 0, fields: 4, methods: 3, attributes: 0 //常量池
Constant pool: #1 = Methodref #7.#30 // java/lang/Object."<init>":()V #2 = Fieldref #31.#32 // java/lang/System.out:Ljava/io/PrintStream; #3 = String #33 // Hi, friend! #4 = Methodref #34.#35 // java/io/PrintStream.println:(Ljava/lang/String;)V #5 = String #36 // xyz #6 = Class #37 // jvm/classStructure/ClassStructure #7 = Class #38 // java/lang/Object #8 = Utf8 _e #9 = Utf8 D #10 = Utf8 ConstantValue #11 = Double 2.73d #13 = Utf8 name #14 = Utf8 Ljava/lang/String; #15 = Utf8 age #16 = Utf8 I #17 = Utf8 hobbies #18 = Utf8 [Ljava/lang/String; #19 = Utf8 <init> #20 = Utf8 ()V #21 = Utf8 Code #22 = Utf8 LocalVariableTable #23 = Utf8 this #24 = Utf8 Ljvm/classStructure/ClassStructure; #25 = Utf8 sayHi #26 = Utf8 printNum #27 = Utf8 j #28 = Utf8 i #29 = Utf8 StackMapTable #30 = NameAndType #19:#20 // "<init>":()V #31 = Class #39 // java/lang/System #32 = NameAndType #40:#41 // out:Ljava/io/PrintStream; #33 = Utf8 Hi, friend! #34 = Class #42 // java/io/PrintStream #35 = NameAndType #43:#44 // println:(Ljava/lang/String;)V #36 = Utf8 xyz #37 = Utf8 jvm/classStructure/ClassStructure #38 = Utf8 java/lang/Object #39 = Utf8 java/lang/System #40 = Utf8 out #41 = Utf8 Ljava/io/PrintStream; #42 = Utf8 java/io/PrintStream #43 = Utf8 println #44 = Utf8 (Ljava/lang/String;)V {
//属性 private static final double _e; descriptor: D flags: (0x001a) ACC_PRIVATE, ACC_STATIC, ACC_FINAL ConstantValue: double 2.73d private java.lang.String name; descriptor: Ljava/lang/String; flags: (0x0002) ACC_PRIVATE public int age; descriptor: I flags: (0x0001) ACC_PUBLIC public java.lang.String[] hobbies; descriptor: [Ljava/lang/String; flags: (0x0001) ACC_PUBLIC //方法 public jvm.classStructure.ClassStructure(); descriptor: ()V flags: (0x0001) ACC_PUBLIC Code:
//这里的locals=1和args_size=1是因为编译器传了this这个参数给方法 stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Ljvm/classStructure/ClassStructure; public static void sayHi(); descriptor: ()V flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code:
//因为是静态方法,所以编译器不会传this进去,所以locals=0,args_size=0 stack=2, locals=0, args_size=0 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #3 // String Hi, friend! 5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return public void printNum(); descriptor: ()V flags: (0x0001) ACC_PUBLIC Code: stack=2, locals=3, args_size=1 0: iconst_0 1: istore_1 2: iload_1 3: bipush 10 5: if_icmpge 36 8: iconst_0 9: istore_2 10: iload_2 11: bipush 15 13: if_icmpge 30 16: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 19: ldc #5 // String xyz 21: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 24: iinc 2, 1 27: goto 10 30: iinc 1, 1 33: goto 2 36: return
//局部变量表,显示了局部变量和它们代号之间的对应关系 LocalVariableTable: Start Length Slot Name Signature 10 20 2 j I 2 34 1 i I 0 37 0 this Ljvm/classStructure/ClassStructure; StackMapTable: number_of_entries = 4 frame_type = 252 /* append */ offset_delta = 2 locals = [ int ] frame_type = 252 /* append */ offset_delta = 7 locals = [ int ] frame_type = 250 /* chop */ offset_delta = 19 frame_type = 250 /* chop */ offset_delta = 5 }