优秀的编程知识分享平台

网站首页 > 技术文章 正文

JVM系列-6.javap指令介绍(jvm调优)

nanyue 2024-10-27 11:23:21 技术文章 1 ℃

上篇JVM系列-5.认识class文件介绍了class文件基本结构,这里我们用javap指令看看字节码文件的内容。

首先,介绍下javap指令,javap是jdk自带的反解析工具。它的作用就是根据class字节码文件,反解析出当前类对应的code区(汇编指令)、本地变量表、异常表和代码行偏移量映射表、常量池等等信息。当然这些信息中,有些信息(如本地变量表、指令和代码行偏移量映射表、常量池中方法的参数名称等等)需要在使用javac编译成class文件时,指定参数才能输出,比如,你直接javac xx.java,就不会再生成对应的局部变量表等信息,如果你使用javac -g xx.java就可以生成所有相关信息了。如果你使用的eclipse,则默认情况下,eclipse在编译时会帮你生成局部变量表、指令和代码行偏移量映射表等信息的。

通过反编译生成的汇编代码,我们可以深入的了解java代码的工作机制。比如我们可以查看i++;这行代码实际运行时是先获取变量i的值,然后将这个值加1,最后再将加1后的值赋值给变量i。通过局部变量表,我们可以查看局部变量的作用域范围、所在槽位等信息,甚至可以看到槽位复用等信息。

javap的用法格式:

javap <options> <classes>

其中classes就是你要反编译的class文件。在命令行中直接输入javap或javap -help可以看到javap的options有如下选项:

apple@hzdliuying6 ~ % javap
用法: javap <options> <classes>
其中, 可能的选项包括:
  -help  --help  -?        输出此用法消息
  -version                 版本信息
  -v  -verbose             输出附加信息
  -l                       输出行号和本地变量表
  -public                  仅显示公共类和成员
  -protected               显示受保护的/公共类和成员
  -package                 显示程序包/受保护的/公共类
                           和成员 (默认)
  -p  -private             显示所有类和成员
  -c                       对代码进行反汇编
  -s                       输出内部类型签名
  -sysinfo                 显示正在处理的类的
                           系统信息 (路径, 大小, 日期, MD5 散列)
  -constants               显示最终常量
  -classpath <path>        指定查找用户类文件的位置
  -cp <path>               指定查找用户类文件的位置
  -bootclasspath <path>    覆盖引导类文件的位置

一般常用的是-v -l -c三个选项。

javap -v classxx,不仅会输出行号、本地变量表信息、反编译汇编代码,还会输出当前类用到的常量池等信息。

javap -l 会输出行号和本地变量表信息。

javap -c 会对当前class字节码进行反编译生成汇编代码。

查看汇编代码时,需要知道里面的jvm指令,可以参考官方文档:https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html

另外通过jclasslib工具也可以看到上面这些信息,而且是可视化的,效果更好一些。

javap测试及内容详解

前面已经介绍过javap输出的内容有哪些,东西比较多,这里主要介绍其中code区(汇编指令)、局部变

量表和代码行偏移映射三个部分。

如果需要分析更多的信息,可以使用javap -v进行查看。

另外,为了更方便理解,所有汇编指令不单拎出来讲解,而是在反汇编代码中以注释的方式讲解。

下面用之前的TestJVM.class测试一下,代码如下:

package jvm;

public class TestJVM {
	public static void main(String[] args) {
		String str = System.getProperty("str");
		if (str == null) {
			System.out.println("not exist");
		} else {
			System.out.println(str);
		}
	}
}

16进制查看:

//cafe babe是魔数 0000是副版本号 0034是父版本号,对应十进制52表明使用的是jdk8 
//002c表示常量池计数器,10进制为44,当然可以继续分析下去,
//比如0a00中oa是常量池项cp_info中的tag值,10表示类中方法,
//但是这么看太累了,所以我们下面是用javap指令辅助查看
cafe babe 0000 0034 002c 0a00 0800 1a08  
0014 0a00 1b00 1c09 001b 001d 0800 1e0a
001f 0020 0700 2107 0022 0100 063c 696e
6974 3e01 0003 2829 5601 0004 436f 6465
0100 0f4c 696e 654e 756d 6265 7254 6162
6c65 0100 124c 6f63 616c 5661 7269 6162
6c65 5461 626c 6501 0004 7468 6973 0100
0d4c 6a76 6d2f 5465 7374 4a56 4d3b 0100
046d 6169 6e01 0016 285b 4c6a 6176 612f
6c61 6e67 2f53 7472 696e 673b 2956 0100
0461 7267 7301 0013 5b4c 6a61 7661 2f6c
616e 672f 5374 7269 6e67 3b01 0003 7374
7201 0012 4c6a 6176 612f 6c61 6e67 2f53
7472 696e 673b 0100 0d53 7461 636b 4d61
7054 6162 6c65 0700 2301 000a 536f 7572
6365 4669 6c65 0100 0c54 6573 744a 564d
2e6a 6176 610c 0009 000a 0700 240c 0025
0026 0c00 2700 2801 0009 6e6f 7420 6578
6973 7407 0029 0c00 2a00 2b01 000b 6a76
6d2f 5465 7374 4a56 4d01 0010 6a61 7661
2f6c 616e 672f 4f62 6a65 6374 0100 106a
6176 612f 6c61 6e67 2f53 7472 696e 6701
0010 6a61 7661 2f6c 616e 672f 5379 7374
656d 0100 0b67 6574 5072 6f70 6572 7479
0100 2628 4c6a 6176 612f 6c61 6e67 2f53
7472 696e 673b 294c 6a61 7661 2f6c 616e
672f 5374 7269 6e67 3b01 0003 6f75 7401
0015 4c6a 6176 612f 696f 2f50 7269 6e74
5374 7265 616d 3b01 0013 6a61 7661 2f69
6f2f 5072 696e 7453 7472 6561 6d01 0007
7072 696e 746c 6e01 0015 284c 6a61 7661
2f6c 616e 672f 5374 7269 6e67 3b29 5600
2100 0700 0800 0000 0000 0200 0100 0900
0a00 0100 0b00 0000 2f00 0100 0100 0000
052a b700 01b1 0000 0002 000c 0000 0006
0001 0000 0003 000d 0000 000c 0001 0000
0005 000e 000f 0000 0009 0010 0011 0001
000b 0000 0070 0002 0002 0000 001d 1202
b800 034c 2bc7 000e b200 0412 05b6 0006
a700 0ab2 0004 2bb6 0006 b100 0000 0300
0c00 0000 1600 0500 0000 0500 0600 0600
0a00 0700 1500 0900 1c00 0b00 0d00 0000
1600 0200 0000 1d00 1200 1300 0000 0600
1700 1400 1500 0100 1600 0000 0900 02fc
0015 0700 1706 0001 0018 0000 0002 0019

使用javap -c -l TestJVM.class之后结果:

Compiled from "TestJVM.java" //编译自TestJVM.java
public class jvm.TestJVM {
  //默认的构造方法,在构造方法执行时主要完成一些初始化操作,包括一些成员变量的初始化赋值 等操作
  public jvm.TestJVM();
    Code:
       //从本地变量表中加载索引为0(这里0对应下面本地变量表中的0)的变量的值,也即this的引用,压入栈
       0: aload_0  
       //出栈,调用java/lang/Object."<init>":()V 初始化 对象,
       //就是this指定的对象的<init>方法完成初始化
       1: invokespecial #1        //Method java/lang/Object."<init>":()V
       4: return
  //指令与代码行数的偏移对应关系,每一行第一个数字对应代码行数,
  //第二个数字对应前面code中指 令前面的数字
    LineNumberTable: 
      line 3: 0
  //局部变量表,start+length表示这个变量在字节码中的生命周期起始和结束的偏移位置 
  //(this生命周期从0开始,长度是5),
  //slot就是这个变量在局部变量表中的槽位(槽位可复用),
  //name就是变量名称,Signatur局部变量类型描述
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0       5     0  this   Ljvm/TestJVM;

  public static void main(java.lang.String[]);
    Code:
       0: ldc           #2                  // String str 将常量str压入栈中
       2: invokestatic  #3                  // Method java/lang/System.getProperty:(Ljava/lang/String;)Ljava/lang/String;调用静态方法
       5: astore_1
       6: aload_1
       7: ifnonnull     21                  //如果不空转到21
      10: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;获取静态对象
      13: ldc           #5                  // String not exist 将常量“not exist”压入栈中
      15: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V invokevirtual指令表示基于 类调用方法
      18: goto          28                  //转到28返回
      21: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream; 同上
      24: aload_1                           //从局部变量表中取出str变量
      25: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V  同上
      28: return
    LineNumberTable:
      line 5: 0
      line 6: 6
      line 7: 10
      line 9: 21
      line 11: 28
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0      29     0  args   [Ljava/lang/String;
          6      23     1   str   Ljava/lang/String;
}

使用javap -v TestJVM.class查看结果,可以看到-v方式展示的更详尽:

Classfile /Users/tapple/Documents/doc/java/19-jvm/1/jvm/bin/jvm/TestJVM.class
  Last modified 2020-6-15; size 720 bytes //最近修改时间和文件大小
  MD5 checksum 5096c7d2cce573b20c5f0d80d031f150 //MD5较验和,文件验证使用
  Compiled from "TestJVM.java" //编译自TestJVM.java
public class jvm.TestJVM //类名
  minor version: 0  //副版本号
  major version: 52  //主版本号
  flags: ACC_PUBLIC, ACC_SUPER //访问标志
Constant pool: //常量池
   #1 = Methodref          #8.#26         // java/lang/Object."<init>":()V
   #2 = String             #20            // str
   #3 = Methodref          #27.#28        // java/lang/System.getProperty:(Ljava/lang/String;)Ljava/lang/String;
   #4 = Fieldref           #27.#29        // java/lang/System.out:Ljava/io/PrintStream;
   #5 = String             #30            // not exist
   #6 = Methodref          #31.#32        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #7 = Class              #33            // jvm/TestJVM
   #8 = Class              #34            // java/lang/Object
   #9 = Utf8               <init>
  #10 = Utf8               ()V
  #11 = Utf8               Code
  #12 = Utf8               LineNumberTable
  #13 = Utf8               LocalVariableTable
  #14 = Utf8               this
  #15 = Utf8               Ljvm/TestJVM;
  #16 = Utf8               main
  #17 = Utf8               ([Ljava/lang/String;)V
  #18 = Utf8               args
  #19 = Utf8               [Ljava/lang/String;
  #20 = Utf8               str
  #21 = Utf8               Ljava/lang/String;
  #22 = Utf8               StackMapTable
  #23 = Class              #35            // java/lang/String
  #24 = Utf8               SourceFile
  #25 = Utf8               TestJVM.java
  #26 = NameAndType        #9:#10         // "<init>":()V
  #27 = Class              #36            // java/lang/System
  #28 = NameAndType        #37:#38        // getProperty:(Ljava/lang/String;)Ljava/lang/String;
  #29 = NameAndType        #39:#40        // out:Ljava/io/PrintStream;
  #30 = Utf8               not exist
  #31 = Class              #41            // java/io/PrintStream
  #32 = NameAndType        #42:#43        // println:(Ljava/lang/String;)V
  #33 = Utf8               jvm/TestJVM
  #34 = Utf8               java/lang/Object
  #35 = Utf8               java/lang/String
  #36 = Utf8               java/lang/System
  #37 = Utf8               getProperty
  #38 = Utf8               (Ljava/lang/String;)Ljava/lang/String;
  #39 = Utf8               out
  #40 = Utf8               Ljava/io/PrintStream;
  #41 = Utf8               java/io/PrintStream
  #42 = Utf8               println
  #43 = Utf8               (Ljava/lang/String;)V
{
  public jvm.TestJVM(); //构造方法
    descriptor: ()V //方法参数及返回描述
    flags: ACC_PUBLIC //访问控制标志
    Code: //以下类似-c -l查询的内容
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Ljvm/TestJVM;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: ldc           #2                  // String str
         2: invokestatic  #3                  // Method java/lang/System.getProperty:(Ljava/lang/String;)Ljava/lang/String;
         5: astore_1
         6: aload_1
         7: ifnonnull     21
        10: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
        13: ldc           #5                  // String not exist
        15: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        18: goto          28
        21: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
        24: aload_1
        25: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        28: return
      LineNumberTable:
        line 5: 0
        line 6: 6
        line 7: 10
        line 9: 21
        line 11: 28
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      29     0  args   [Ljava/lang/String;
            6      23     1   str   Ljava/lang/String;
      StackMapTable: number_of_entries = 2
        frame_type = 252 /* append */
          offset_delta = 21
          locals = [ class java/lang/String ]
        frame_type = 6 /* same */
}

以上是javap指令简单介绍,下篇分析常量池部分

Tags:

最近发表
标签列表