[JVM] 用Javap分析字节码文件

扶醉桌前 提交于 2019-12-17 04:14:14

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
}

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!