天天看點

Java位元組碼深入解析 || 使用Intellij idea如何快速檢視Java類位元組碼

注意: javap 檢視【class檔案的位元組碼】資訊                                                                                                                       

Java位元組碼深入解析

一:Java位元組代碼的組織形式

  類檔案{

  OxCAFEBABE,小版本号,大版本号,常量池大小,常量池數組,通路控制标記,目前類資訊,父類資訊,實作的接口個數,實作的接口資訊數組,域個數,域資訊數組,方法個數,方法資訊數組,屬性個數,屬性資訊數組

  }

  二:檢視方法 --- javap指令

  例子:有一個Java類Demo.java

/**
 * @author honglei
 * @since 2019-08-11
 */
public class Demo {
    /**
     * str1
     */
    private String str1;
    /**
     * str2
     */
    private String str2;
    /**
     * num1
     */
    private int num1;
    /**
     * num2
     */
    private int num2;
    /**
     * str3
     */
    public static final String STATIC_DATA = "hello world";

    /**
     * str1
     */
    private void sayHello1() {
        System.out.println("this is method1...");
    }

    /**
     * sayHello2
     */
    private void sayHello2() {
        System.out.println("this is method2...");
    }

    /**
     * sayHello3
     */
    private void sayHello3() {
        System.out.println("this is method3...");
    }
}           

  通過jdk自帶的反編譯工具指令 javap 可以檢視class檔案的位元組碼資訊

D:\>javap -verbose Demo >> Demo.txt

  Demo.txt:

sai:springbootdemo ws$ javap -verbose Demo.class 
Classfile /Users/ws/dev/SourceTree/springboot/springboot-demo/target/classes/com/example/springbootdemo/Demo.class
  Last modified Aug 11, 2019; size 1055 bytes
  MD5 checksum 98bea2d49bf0c5386ac21e3cae91d404
  Compiled from "Demo.java"
public class com.example.springbootdemo.Demo
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #9.#36         // java/lang/Object."<init>":()V
   #2 = Fieldref           #37.#38        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = String             #39            // this is method1...
   #4 = Methodref          #40.#41        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #5 = String             #42            // this is method2...
   #6 = String             #43            // this is method3...
   #7 = String             #44            // test
   #8 = Class              #45            // com/example/springbootdemo/Demo
   #9 = Class              #46            // java/lang/Object
  #10 = Utf8               str1
  #11 = Utf8               Ljava/lang/String;
  #12 = Utf8               str2
  #13 = Utf8               num1
  #14 = Utf8               I
  #15 = Utf8               num2
  #16 = Utf8               STATIC_DATA
  #17 = Utf8               ConstantValue
  #18 = String             #47            // hello world
  #19 = Utf8               <init>
  #20 = Utf8               ()V
  #21 = Utf8               Code
  #22 = Utf8               LineNumberTable
  #23 = Utf8               LocalVariableTable
  #24 = Utf8               this
  #25 = Utf8               Lcom/example/springbootdemo/Demo;
  #26 = Utf8               sayHello1
  #27 = Utf8               sayHello2
  #28 = Utf8               sayHello3
  #29 = Utf8               main
  #30 = Utf8               ([Ljava/lang/String;)V
  #31 = Utf8               args
  #32 = Utf8               [Ljava/lang/String;
  #33 = Utf8               MethodParameters
  #34 = Utf8               SourceFile
  #35 = Utf8               Demo.java
  #36 = NameAndType        #19:#20        // "<init>":()V
  #37 = Class              #48            // java/lang/System
  #38 = NameAndType        #49:#50        // out:Ljava/io/PrintStream;
  #39 = Utf8               this is method1...
  #40 = Class              #51            // java/io/PrintStream
  #41 = NameAndType        #52:#53        // println:(Ljava/lang/String;)V
  #42 = Utf8               this is method2...
  #43 = Utf8               this is method3...
  #44 = Utf8               test
  #45 = Utf8               com/example/springbootdemo/Demo
  #46 = Utf8               java/lang/Object
  #47 = Utf8               hello world
  #48 = Utf8               java/lang/System
  #49 = Utf8               out
  #50 = Utf8               Ljava/io/PrintStream;
  #51 = Utf8               java/io/PrintStream
  #52 = Utf8               println
  #53 = Utf8               (Ljava/lang/String;)V
{
  public static final java.lang.String STATIC_DATA;
    descriptor: Ljava/lang/String;
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
    ConstantValue: String hello world

  public com.example.springbootdemo.Demo();
    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 7: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/example/springbootdemo/Demo;

  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: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #7                  // String test
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 54: 0
        line 55: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  args   [Ljava/lang/String;
    MethodParameters:
      Name                           Flags
      args
}
SourceFile: "Demo.java"
           

解析:

  1、版本号 major version: 49 //java版本 jdk1.6顯示的是50, jdk1.5顯示的是49,jdk1.4顯示的是58 , 高版本能執行低版本的class檔案

  2、常量池Constant pool

  Method:方法

  Field:字段

  String:字元串

  Asciz:簽名如<init>由jvm調用,其他是不能夠去調用它的

  NameAndType:變量名的類型

  Class:類

  通過位元組碼,我們可以看到Demo類 繼承于java.lang.Object,如果類中沒有顯式聲明構造函數的話,編譯器會插入一個預設無參的構造函數(構造函數在JVM級别是顯示成<init>的普通函數)。

  三:檢測代碼的效率問題

  學習Java的過程中,都會了解到字元串合并時要用到StringBuffer 來代替String,那下面就來通過Java位元組碼來驗證兩種方式的效率性。

  例子:一個Java類 TestString.java

  • <strong>public class TestString { 
  •     public String testString(String str1, String str2){ 
  •        return str1 + str2; 
  •     } 
  •     public String testStringBuffer(StringBuffer sb, String str){ 
  •        return sb.append(str).toString(); 
  •  </strong>

  javap –c TestString 後位元組碼資訊:

  • Compiled from "TestString.java" 
  • public class TestString extends java.lang.Object{ 
  • public TestString(); 
  •   Code: 
  •    0:      aload_0 
  •    1:      invokespecial  #8; //Method java/lang/Object."<init>":()V 
  •    4:      return 
  • public java.lang.String testString(java.lang.String, java.lang.String); 
  •    0:      new #16; //class java/lang/StringBuilder 
  •    3:      dup 
  •    4:      aload_1 
  •    5:      invokestatic    #18; //Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String; 
  •    8:      invokespecial  #24; //Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V 
  •    11:     aload_2 
  •    12:    invokevirtual  #27; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 
  •    15:    invokevirtual  #31; //Method java/lang/StringBuilder.toString:()Ljava/lang/String; 
  •    18:    areturn 
  • public java.lang.String testStringBuffer(java.lang.StringBuffer, java.lang.String); 
  •    0:      aload_1 
  •    1:      aload_2 
  •    2:      invokevirtual  #40; //Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer; 
  •    5:      invokevirtual  #45; //Method java/lang/StringBuffer.toString:()Ljava/lang/String; 
  •    8:      areturn 
  • }

  從上面編譯後的位元組碼資訊可以看出來,方法testString 調用了五個方法:new 、invokestatic 、invokespecial 和兩個invokevirtual ; 而testStringBuffer 方法隻調用了兩個invokevirtual 方法。第一個方法比第二個方法多做了好多工作,其效率當然是要低的。而且我們從java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

  可以看出來其實對于String字元串合并,内部還是轉化為StringBuilder的方法調用,這是因為String是長度不可變的,是以不如直接采用StringBuilder(與StringBuffer 長度都是可變的,隻不過前者是非線程安全,後者是線程安全)進行字元串合并。

                                                                 使用Intellij idea如何快速檢視Java類位元組碼

1、打開File-Settings

Java位元組碼深入解析 || 使用Intellij idea如何快速檢視Java類位元組碼

2:打開Tools-External Tools,右側點選綠色“+”

Java位元組碼深入解析 || 使用Intellij idea如何快速檢視Java類位元組碼

3:填寫一些内容規則:Name是在類中,右鍵時使用時的名稱

Java位元組碼深入解析 || 使用Intellij idea如何快速檢視Java類位元組碼

4:代碼處右鍵,即可找到添加的功能

Java位元組碼深入解析 || 使用Intellij idea如何快速檢視Java類位元組碼

5、通過jdk自帶的反編譯工具指令 javap 可以檢視class檔案的位元組碼資訊

-verbose

或者

-c

都可以,詳情可以檢視javap指令如何使用