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.编写代码:
?
??? 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
}?