程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> JVM-String比較-字節碼分析,jvm-string字節碼

JVM-String比較-字節碼分析,jvm-string字節碼

編輯:JAVA綜合教程

JVM-String比較-字節碼分析,jvm-string字節碼


一道String字符串比較問題引發的字節碼分析

public class a {
    public static void main(String[] args)throws Exception{
        
    }
    public static void aa(){
        String s1="a";//內存在方法區的常量池
        String s2="b";//內存在方法區的常量池
        String s12 = "ab";//內存在方法區的常量池
        String s3 = s1 + s2;//s3的內存所在???
        p(s3==s12);//false
    }
    public static void bb(){
        String s1="a"+"b";//s1的內存所在???
        String s2 = "ab";//內存在方法區的常量池
        p(s1==s2);//true
    }public static void p(Object obj){
        System.out.println(obj);
    }
}

這是我們經常碰到的煩人的String比較問題,要得到答案,就要弄清楚aa()方法中的s3的內存在哪裡?,和bb()方法中的s1的內存在哪裡?

不多說,貼上a.class文件反編譯的字節碼指令:

首先是 aa()方法:

public static void aa();
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=4, args_size=0                                                  共4個本地變量空間
         0: ldc           #3                  // String a                            將字符串"a"從常量池中推送至棧頂
         2: astore_0                                                                  將棧頂引用類型(即字符串"a")存入第一個本地變量
         3: ldc           #4                  // String b                              將字符串"b"從常量池推送至棧頂
         5: astore_1                                                                  將棧頂引用類型(即字符串"b")存入第二個本地變量
         6: ldc           #5                  // String ab                             將字符串"ab"從常量池推送至棧頂
         8: astore_2                                                                  將棧頂引用類型(即字符串"ab")存入第三個本地變量
         9: new           #6                  // class java/lang/StringBuilder         創建StringBuilder對象sb,並將引用值壓入棧頂
        12: dup                                                                       復制棧頂數值,並將復制值壓入棧頂
        13: invokespecial #7                  // Method java/lang/StringBuilder.       調用對象的初始化方法
"<init>":()V
        16: aload_0                                                                   將第一個本地變量(即字符串"a")推送至棧頂
        17: invokevirtual #8                  // Method java/lang/StringBuilder.       調用實例方法sb.append("a");
append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        20: aload_1                                                                   將第二個本地變量(即字符串"b")推送至棧頂
        21: invokevirtual #8                  // Method java/lang/StringBuilder.       調用實例方法sb.append("b");
append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        24: invokevirtual #9                  // Method java/lang/StringBuilder.       調用實例方法sb.toString(),並將結果【Java堆地址】放在棧頂
toString:()Ljava/lang/String;
        27: astore_3                                                                   將棧頂引用類型(即堆地址)存入第四個本地變量                                                            
        28: aload_3                                                                    將第四個本地變量(即堆地址)推送至棧頂
        29: aload_2                                                                    將第三個本地變量(即字符串"ab")推送至棧頂
        30: if_acmpne     37                                                           比較棧頂兩引用數值,結果不同跳轉(當然不同啦)
        33: iconst_1
        34: goto          38
        37: iconst_0                                                                   將int類型 0 壓入棧頂
        38: invokestatic  #10                 // Method java/lang/Boolean.valueO       調用靜態方法Boolean.valueOf();實現基本數據類型->包裝類型自動轉換
f:(Z)Ljava/lang/Boolean;
        41: invokestatic  #11                 // Method p:(Ljava/lang/Object;)V        調用靜態方法p(false);//輸出false
        44: return                                                                     從當前方法返回void

 針對其中的一些解釋:(下面的數字是字節碼偏移量)

24       為何在sb.toString()我說的是【堆地址】,大家看源碼就知道了。

//這是StringBuilder的源碼,返回的是堆上的字符串地址
public String toString() {
    return new String(value, 0, count);
}

所以在aa()方法中,s3的內存其實在Java堆上,s12在方法區的常量池上,所以兩者不相等。

 

37      boolean到底分配幾個字節,在這裡大家可以看到。

如果為true,編譯器翻譯的字節碼是iconst_1,意思將int類型1存入棧頂,所以單個引用boolean值時,分配4個字節,和int相同。(數組boolean沒測試,不清楚)

如果為false,編譯器翻譯的字節碼是iconst_0,意思將int類型0存入棧頂。

 

38      在這裡我們還能看到自動類型轉換的身影,這裡是基本數據類型boolean->包裝類Boolean的自動類型轉換,實際調用的就是Boolean.valueOf()靜態方法,這是因為下面的p()方法裡面需要的是Object引用類型,所以進行了自動類型轉換。

 

然後是 bb()方法:

public static void bb();
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=0                                                   兩個本地變量空間
         0: ldc           #5                  // String ab                             將字符串"ab"從常量池中推送至棧頂
         2: astore_0                                                                   將棧頂引用類型(字符串"ab")存入第一個本地變量
         3: ldc           #5                  // String ab                             將字符串"ab"從常量池中推送至棧頂
         5: astore_1                                                                   將棧頂引用類型(字符串"ab")存入第一個本地變量
         6: aload_0                                                                    將第一個本地變量("ab")推送至棧頂
         7: aload_1                                                                    將第二個本地變量("ab")推送至棧頂
         8: if_acmpne     15                                                           比較棧頂兩引用類型數值,結果不同跳轉(這裡當然相同啦)
        11: iconst_1                                                                   將int類型 1 推送至棧頂
        12: goto          16                                                           無條件跳轉到16字節碼偏移量
        15: iconst_0
        16: invokestatic  #10                 // Method java/lang/Boolean.valueO       調用靜態方法Boolean.valueOf();並將返回的Boolean類型的true壓入棧頂
f:(Z)Ljava/lang/Boolean;                                            
        19: invokestatic  #11                 // Method p:(Ljava/lang/Object;)V        調用靜態方法p(true);輸出true
        22: return                                                                     從當前方法返回void

 針對其中的一些解釋:(下面的數字是字節碼偏移量)

0           大家看到了吧,編譯器看到String a="aa"+"bb";會自動合並,將"aabb"存入常量池,並返回地址。所以答案為true。

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved