天天看點

JVM編譯期字元串連接配接優化分析

為了研究javac對于String相關代碼的位元組碼優化,我做了如下測試。

    測試環境:

       $ javac -version

       javac 1.6.0_23

       $ java -version

       java version "1.6.0_23"

       OpenJDK Runtime Environment (IcedTea6 1.11pre) (6b23~pre11-0ubuntu1.11.10.2)

       OpenJDK 64-Bit Server VM (build 20.0-b11, mixed mode)

    1.編寫代碼:

String name = "ab"+"cd";
String name1 = "ab" + new String("cd");
String name2 = "ab"+1+"cd";
String name3 = "ab"+"c" + new String("d");
StringBuffer name4 = new StringBuffer("e");
name4.append("f").append("g");
           

    2.執行編譯: javac StringAdd.java

    生成位元組碼StringAdd.class

    3.反編譯:javap -c -l -verbose StringAdd > StringAdd.javap

    生成反編譯檔案。

    main方法如下(此處隻列出主要部分,完整檔案見附件):

   0:    ldc #2; //String abcd

#字元串常量連接配接,已經過常量折疊。

   2:    astore_1

   3:    new #3; //class java/lang/StringBuilder

   6:    dup

   7:    invokespecial #4; //Method java/lang/StringBuilder."<init>":()V

   10:   ldc #5; //String ab

   12:   invokevirtual #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

   15:   new #7; //class java/lang/String

   18:   dup

   19:   ldc #8; //String cd  。

   21:   invokespecial #9; //Method java/lang/String."<init>":(Ljava/lang/String;)V

   24:   invokevirtual #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

   27:   invokevirtual #10; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;

   30:   astore_2

# 通過構造方法建立的String常量沒有折疊,但字元串連接配接操作已被StringBuilder取代

   31:   ldc #11; //String ab1cd

   33:   astore_3

# 有類型轉換的常量字元串連接配接操作也被已常量折疊方式優化

   34:   new #3; //class java/lang/StringBuilder

   37:   dup

   38:   invokespecial #4; //Method java/lang/StringBuilder."<init>":()V

   41:   ldc #12; //String abc

   43:   invokevirtual #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

   46:   new #7; //class java/lang/String

   49:   dup

   50:   ldc #13; //String d

   52:   invokespecial #9; //Method java/lang/String."<init>":(Ljava/lang/String;)V

   55:   invokevirtual #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

   58:   invokevirtual #10; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;

   61:   astore 4

# 同時包含字面常量和通過構造方法構造的String的連接配接,字面常量部分被已常量折疊方式優化,剩餘部分的字元串連接配接被StringBuilder取代。

   63:   new #14; //class java/lang/StringBuffer

   66:   dup

   67:   ldc #15; //String e

   69:   invokespecial #16; //Method java/lang/StringBuffer."<init>":(Ljava/lang/String;)V

   72:   astore 5

   74:   aload  5

   76:   ldc #17; //String f

   78:   invokevirtual #18; //Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;

   81:   ldc #19; //String g

   83:   invokevirtual #18; //Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;

# 通過StringBuffer構造字元串沒有被優化。

   86:   pop

   87:   return

    分析:

    通過上面對反編譯位元組碼的分析,我們可以看到編譯器在編譯期對代碼的優化特點:

    1 常量折疊,将編譯期可以計算出的靜态結果提前得出,将運作時計算開銷降低為0。

    2 自動将字元串連接配接操作優化為StringBuilder類的append方法,以提高連接配接速度。

    3 對于調用方法動态生成的對象無法以常量折疊方式優化

    通過分析可以得知:

    1 為了提高可讀性,将一個字元串常量分割為多個用于排版,不會影響運作效率;

    2 為了提高可讀性,可以直接使用字元串連接配接符“+”,替換非線程同步的StringBuilder類,不會對運作時效率産生影響

3 使用單線程下使用StringBuffer反而會降低性能。猜測:1.5以前的版本可能沒有差別,或使用StringBuffer更快,因為1.5才開始有StringBuilder類,也不知道1.5以前的編譯器會不會使用StringBuffer替換字元串連接配接操作。

  引申:

    存在域定義

    static final String a="a";

    static String b="b";

    static final String c=new String("c");

    反編譯方法内部代碼

    String name1 = "1"+a;

    String name2 = "2"+b;

    String name3 = "3"+c;

    效果如下:

   0:   ldc #2; //String 1a

   2:   astore_1

# 靜态常量被直接折疊儲存。

   3:   new #3; //class java/lang/StringBuilder

   6:   dup

   7:   invokespecial #4; //Method java/lang/StringBuilder."<init>":()V

   10:  ldc #5; //String 2

   12:  invokevirtual #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

   15:  getstatic  #7; //Field b:Ljava/lang/String;

   18:  invokevirtual #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

   21:  invokevirtual #8; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;

   24:  astore_2

# 變量未被折疊

   25:  new #3; //class java/lang/StringBuilder

   28:  dup

   29:  invokespecial #4; //Method java/lang/StringBuilder."<init>":()V

   32:  ldc #9; //String 3

   34:  invokevirtual #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

   37:  getstatic  #10; //Field c:Ljava/lang/String;

   40:  invokevirtual #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

   43:  invokevirtual #8; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;

   46:  astore_3

#  動态常量未被折疊儲存

    分析:

    1 常量可以被認為運作時不可改變,是以編譯時被以常量折疊方式優化。

    2 變量和動态生成的常量必須在運作時确定值,是以不能在編譯期折疊優化

    結論:

    如class1 中某個地方直接引用了 class2中的某個final常量,則在編譯時會将常量值記入class1的常量池中,或被常量折疊優化。如果class2修改了這個常量并重新編譯,運作時class1中的值不會随之變動,而是使用舊的class2的值,導緻程式出現不可預期的效果。

    是以建議 通過動态指派方式給常量指派,如:

       final String str = new String(“str1”);

       final int one = new Integer(1);

    雖然增加了類初始化的時間,但可以保證final值所在class檔案更新後其他class不用重新編譯就可以使用新的值。(雖然随便修改final定義是不好的。。。)

附件:

用到以下2個類。

public class StringAdd{

    public static void main(String[] arg){

String name = "ab"+"cd";

String name1 = "ab" + new String("cd");

String name2 = "ab"+1+"cd";

String name3 = "ab"+"c" + new String("d");

StringBuffer name4 = new StringBuffer("e");

name4.append("f").append("g");

    }

}

public class StringAdd2{

static final String a="a";

static String b="b";

static final String c=new String("c");

public static void main(String[] args){

String name1 = "1"+a;

String name2 = "2"+b;

String name3 = "3"+c;

}

}

反編譯結果如下:

Compiled from "StringAdd.java"

public class StringAdd extends java.lang.Object

  SourceFile: "StringAdd.java"

  minor version: 0

  major version: 50

  Constant pool:

const #1 = Method #21.#30; //  java/lang/Object."<init>":()V

const #2 = String #31; //  abcd

const #3 = class #32; //  java/lang/StringBuilder

const #4 = Method #3.#30; //  java/lang/StringBuilder."<init>":()V

const #5 = String #33; //  ab

const #6 = Method #3.#34; //  java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

const #7 = class #35; //  java/lang/String

const #8 = String #36; //  cd

const #9 = Method #7.#37; //  java/lang/String."<init>":(Ljava/lang/String;)V

const #10 = Method #3.#38; //  java/lang/StringBuilder.toString:()Ljava/lang/String;

const #11 = String #39; //  ab1cd

const #12 = String #40; //  abc

const #13 = String #41; //  d

const #14 = class #42; //  java/lang/StringBuffer

const #15 = String #43; //  e

const #16 = Method #14.#37; //  java/lang/StringBuffer."<init>":(Ljava/lang/String;)V

const #17 = String #44; //  f

const #18 = Method #14.#45; //  java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;

const #19 = String #46; //  g

const #20 = class #47; //  StringAdd

const #21 = class #48; //  java/lang/Object

const #22 = Asciz <init>;

const #23 = Asciz ()V;

const #24 = Asciz Code;

const #25 = Asciz LineNumberTable;

const #26 = Asciz main;

const #27 = Asciz ([Ljava/lang/String;)V;

const #28 = Asciz SourceFile;

const #29 = Asciz StringAdd.java;

const #30 = NameAndType #22:#23;//  "<init>":()V

const #31 = Asciz abcd;

const #32 = Asciz java/lang/StringBuilder;

const #33 = Asciz ab;

const #34 = NameAndType #49:#50;//  append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

const #35 = Asciz java/lang/String;

const #36 = Asciz cd;

const #37 = NameAndType #22:#51;//  "<init>":(Ljava/lang/String;)V

const #38 = NameAndType #52:#53;//  toString:()Ljava/lang/String;

const #39 = Asciz ab1cd;

const #40 = Asciz abc;

const #41 = Asciz d;

const #42 = Asciz java/lang/StringBuffer;

const #43 = Asciz e;

const #44 = Asciz f;

const #45 = NameAndType #49:#54;//  append:(Ljava/lang/String;)Ljava/lang/StringBuffer;

const #46 = Asciz g;

const #47 = Asciz StringAdd;

const #48 = Asciz java/lang/Object;

const #49 = Asciz append;

const #50 = Asciz (Ljava/lang/String;)Ljava/lang/StringBuilder;;

const #51 = Asciz (Ljava/lang/String;)V;

const #52 = Asciz toString;

const #53 = Asciz ()Ljava/lang/String;;

const #54 = Asciz (Ljava/lang/String;)Ljava/lang/StringBuffer;;

{

public StringAdd();

  LineNumberTable:

   line 1: 0

  Code:

   Stack=1, Locals=1, Args_size=1

   0: aload_0

   1: invokespecial #1; //Method java/lang/Object."<init>":()V

   4: return

  LineNumberTable:

   line 1: 0

public static void main(java.lang.String[]);

  LineNumberTable:

   line 3: 0

   line 4: 3

   line 5: 31

   line 6: 34

   line 7: 63

   line 8: 74

   line 9: 87

  Code:

   Stack=4, Locals=6, Args_size=1

   0: ldc #2; //String abcd

   2: astore_1

   3: new #3; //class java/lang/StringBuilder

   6: dup

   7: invokespecial #4; //Method java/lang/StringBuilder."<init>":()V

   10: ldc #5; //String ab

   12: invokevirtual #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

   15: new #7; //class java/lang/String

   18: dup

   19: ldc #8; //String cd

   21: invokespecial #9; //Method java/lang/String."<init>":(Ljava/lang/String;)V

   24: invokevirtual #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

   27: invokevirtual #10; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;

   30: astore_2

   31: ldc #11; //String ab1cd

   33: astore_3

   34: new #3; //class java/lang/StringBuilder

   37: dup

   38: invokespecial #4; //Method java/lang/StringBuilder."<init>":()V

   41: ldc #12; //String abc

   43: invokevirtual #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

   46: new #7; //class java/lang/String

   49: dup

   50: ldc #13; //String d

   52: invokespecial #9; //Method java/lang/String."<init>":(Ljava/lang/String;)V

   55: invokevirtual #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

   58: invokevirtual #10; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;

   61: astore 4

   63: new #14; //class java/lang/StringBuffer

   66: dup

   67: ldc #15; //String e

   69: invokespecial #16; //Method java/lang/StringBuffer."<init>":(Ljava/lang/String;)V

   72: astore 5

   74: aload 5

   76: ldc #17; //String f

   78: invokevirtual #18; //Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;

   81: ldc #19; //String g

   83: invokevirtual #18; //Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;

   86: pop

   87: return

  LineNumberTable:

   line 3: 0

   line 4: 3

   line 5: 31

   line 6: 34

   line 7: 63

   line 8: 74

   line 9: 87

}

Compiled from "StringAdd2.java"

public class StringAdd2 extends java.lang.Object

  SourceFile: "StringAdd2.java"

  minor version: 0

  major version: 50

  Constant pool:

const #1 = Method #16.#32; //  java/lang/Object."<init>":()V

const #2 = String #33; //  1a

const #3 = class #34; //  java/lang/StringBuilder

const #4 = Method #3.#32; //  java/lang/StringBuilder."<init>":()V

const #5 = String #35; //  2

const #6 = Method #3.#36; //  java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

const #7 = Field #15.#37; //  StringAdd2.b:Ljava/lang/String;

const #8 = Method #3.#38; //  java/lang/StringBuilder.toString:()Ljava/lang/String;

const #9 = String #39; //  3

const #10 = Field #15.#40; //  StringAdd2.c:Ljava/lang/String;

const #11 = String #21; //  b

const #12 = class #41; //  java/lang/String

const #13 = String #22; //  c

const #14 = Method #12.#42; //  java/lang/String."<init>":(Ljava/lang/String;)V

const #15 = class #43; //  StringAdd2

const #16 = class #44; //  java/lang/Object

const #17 = Asciz a;

const #18 = Asciz Ljava/lang/String;;

const #19 = Asciz ConstantValue;

const #20 = String #17; //  a

const #21 = Asciz b;

const #22 = Asciz c;

const #23 = Asciz <init>;

const #24 = Asciz ()V;

const #25 = Asciz Code;

const #26 = Asciz LineNumberTable;

const #27 = Asciz main;

const #28 = Asciz ([Ljava/lang/String;)V;

const #29 = Asciz <clinit>;

const #30 = Asciz SourceFile;

const #31 = Asciz StringAdd2.java;

const #32 = NameAndType #23:#24;//  "<init>":()V

const #33 = Asciz 1a;

const #34 = Asciz java/lang/StringBuilder;

const #35 = Asciz 2;

const #36 = NameAndType #45:#46;//  append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

const #37 = NameAndType #21:#18;//  b:Ljava/lang/String;

const #38 = NameAndType #47:#48;//  toString:()Ljava/lang/String;

const #39 = Asciz 3;

const #40 = NameAndType #22:#18;//  c:Ljava/lang/String;

const #41 = Asciz java/lang/String;

const #42 = NameAndType #23:#49;//  "<init>":(Ljava/lang/String;)V

const #43 = Asciz StringAdd2;

const #44 = Asciz java/lang/Object;

const #45 = Asciz append;

const #46 = Asciz (Ljava/lang/String;)Ljava/lang/StringBuilder;;

const #47 = Asciz toString;

const #48 = Asciz ()Ljava/lang/String;;

const #49 = Asciz (Ljava/lang/String;)V;

{

static final java.lang.String a;

  Constant value: String a

static java.lang.String b;

static final java.lang.String c;

public StringAdd2();

  LineNumberTable:

   line 1: 0

  Code:

   Stack=1, Locals=1, Args_size=1

   0: aload_0

   1: invokespecial #1; //Method java/lang/Object."<init>":()V

   4: return

  LineNumberTable:

   line 1: 0

public static void main(java.lang.String[]);

  LineNumberTable:

   line 6: 0

   line 7: 3

   line 8: 25

   line 9: 47

  Code:

   Stack=2, Locals=4, Args_size=1

   0: ldc #2; //String 1a

   2: astore_1

   3: new #3; //class java/lang/StringBuilder

   6: dup

   7: invokespecial #4; //Method java/lang/StringBuilder."<init>":()V

   10: ldc #5; //String 2

   12: invokevirtual #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

   15: getstatic #7; //Field b:Ljava/lang/String;

   18: invokevirtual #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

   21: invokevirtual #8; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;

   24: astore_2

   25: new #3; //class java/lang/StringBuilder

   28: dup

   29: invokespecial #4; //Method java/lang/StringBuilder."<init>":()V

   32: ldc #9; //String 3

   34: invokevirtual #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

   37: getstatic #10; //Field c:Ljava/lang/String;

   40: invokevirtual #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

   43: invokevirtual #8; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;

   46: astore_3

   47: return

  LineNumberTable:

   line 6: 0

   line 7: 3

   line 8: 25

   line 9: 47

static {};

  LineNumberTable:

   line 3: 0

   line 4: 5

  Code:

   Stack=3, Locals=0, Args_size=0

   0: ldc #11; //String b

   2: putstatic #7; //Field b:Ljava/lang/String;

   5: new #12; //class java/lang/String

   8: dup

   9: ldc #13; //String c

   11: invokespecial #14; //Method java/lang/String."<init>":(Ljava/lang/String;)V

   14: putstatic #10; //Field c:Ljava/lang/String;

   17: return

  LineNumberTable:

   line 3: 0

   line 4: 5

}