常量池和串池「StringTable」的区别。
常量池与串池关系
代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| package com.redisc;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Run") public class Run {
public static void main(String[] args) { String s1 = "a"; String s2 = "b"; String s3 = "ab"; }
}
|
反编译字节码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
| Classfile /Users/licong/java/redisc/target/classes/com/redisc/Run.class Last modified 2022年12月18日; size 689 bytes SHA-256 checksum 3a85c036643acf659cbc934332903161f9dd77a32b36a01a3d76b735d67c2629 Compiled from "Run.java" public class com.redisc.Run minor version: 0 major version: 52 flags: (0x0021) ACC_PUBLIC, ACC_SUPER this_class: super_class: interfaces: 0, fields: 1, methods: 3, attributes: 1 Constant pool: { public com.redisc.Run(); descriptor: ()V flags: (0x0001) ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial 4: return LineNumberTable: line 11: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcom/redisc/Run;
public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=1, locals=4, args_size=1 0: ldc 2: astore_1 3: ldc 5: astore_2 6: ldc 8: astore_3 9: return LineNumberTable: line 14: 0 line 15: 3 line 16: 6 line 17: 9 LocalVariableTable: Start Length Slot Name Signature 0 10 0 args [Ljava/lang/String; 3 7 1 s1 Ljava/lang/String; 6 4 2 s2 Ljava/lang/String; 9 1 3 s3 Ljava/lang/String;
static {}; descriptor: ()V flags: (0x0008) ACC_STATIC Code: stack=1, locals=0, args_size=0 0: ldc 2: invokestatic 5: putstatic 8: return LineNumberTable: line 10: 0 } SourceFile: "Run.java"
|
说一下,运行时候发生了什么。
StringTable
开始的时候是空的,[]
- 常量池中的信息,都会被加载到运行时常量池中
- 这时,
a
、b
、ab
都是常量池中的符号,还没有变为 java
字符串对象「懒惰行为」
- 当用到
a
的时候,也就是 ldc #2
「在反编译中」 会把 a
符号变为 a
字符串
- 这个时候,才会向
StringTable
添加 a
- 此时
StringTable
为 ["a"]
- 其他
b
、ab
都是如此,只有用到时,才会添加进 StringTable
字符串拼接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| package com.redisc;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Run") public class Run {
public static void main(String[] args) { String s1 = "a"; String s2 = "b"; String s3 = "ab"; String s4 = s1 + s2;
System.out.println(s3 == s4); }
}
|
经过反编译
String s4 = s1 + s2;
// 经过反编译这段代码实际上为 new StringBuilder().append(“a”).append(“b”).toString()
toString()
相当于 new String("ab")
所以,s4
本质上是 new String("ab")
根据 java | 堆,这种 new
出的对象,最后都放进了堆中。
而,s3
是放进了串池中,所以,输出为 false
。
编译器的优化
代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| package com.redisc;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Run") public class Run {
public static void main(String[] args) { String s1 = "a"; String s2 = "b"; String s3 = "ab"; String s4 = s1 + s2; String s5 = "a" + "b"; }
}
|
反编译为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
| Classfile /Users/licong/java/redisc/target/classes/com/redisc/Run.class Last modified 2022年12月18日; size 896 bytes SHA-256 checksum 6236b5b6ef4c4af91b0fdc7fcd6fc56790a6366ccfa74f1c78be0521e1914897 Compiled from "Run.java" public class com.redisc.Run minor version: 0 major version: 52 flags: (0x0021) ACC_PUBLIC, ACC_SUPER this_class: super_class: interfaces: 0, fields: 1, methods: 3, attributes: 1 Constant pool: { public com.redisc.Run(); descriptor: ()V flags: (0x0001) ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial 4: return LineNumberTable: line 6: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcom/redisc/Run;
public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=6, args_size=1 0: ldc 2: astore_1 3: ldc 5: astore_2 6: ldc 8: astore_3 9: new 12: dup 13: invokespecial 16: aload_1 17: invokevirtual 20: aload_2 21: invokevirtual 24: invokevirtual 27: astore 4 29: ldc 31: astore 5 33: return LineNumberTable: line 9: 0 line 10: 3 line 11: 6 line 12: 9 line 13: 29 line 14: 33 LocalVariableTable: Start Length Slot Name Signature 0 34 0 args [Ljava/lang/String; 3 31 1 s1 Ljava/lang/String; 6 28 2 s2 Ljava/lang/String; 9 25 3 s3 Ljava/lang/String; 29 5 4 s4 Ljava/lang/String; 33 1 5 s5 Ljava/lang/String;
static {}; descriptor: ()V flags: (0x0008) ACC_STATIC Code: stack=1, locals=0, args_size=0 0: ldc 2: invokestatic 5: putstatic 8: return LineNumberTable: line 5: 0 } SourceFile: "Run.java"
|
去除杂乱信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| Code: stack=2, locals=6, args_size=1 0: ldc #2 2: astore_1 3: ldc #3 5: astore_2 6: ldc #4 8: astore_3 9: new #5 12: dup 13: invokespecial #6 16: aload_1 17: invokevirtual #7 20: aload_2 21: invokevirtual #7 24: invokevirtual #8 27: astore 4 29: ldc #4 31: astore 5 33: return
|
可以看出
29: ldc #4 // String ab
- 是直接去串池找的 ab,并且其执行的是
#4
指令
和 6
号位置是一样的。
所以,29
行代码是直接从常量池中拿到的 ab
,所以,s3
和 s5
是一个对象。
这是 javac
的一个编译优化。因为,javac
认为 s3
和 s5
都是常量,不应该变化了。