0%

java | StringTable

常量池和串池「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: #8 // com/redisc/Run
super_class: #9 // java/lang/Object
interfaces: 0, fields: 1, methods: 3, attributes: 1
Constant pool:
#1 = Methodref #9.#30 // java/lang/Object."<init>":()V
#2 = String #31 // a
#3 = String #32 // b
#4 = String #33 // ab
#5 = String #34 // c.Run
#6 = Methodref #35.#36 // org/slf4j/LoggerFactory.getLogger:(Ljava/lang/String;)Lorg/slf4j/Logger;
#7 = Fieldref #8.#37 // com/redisc/Run.log:Lorg/slf4j/Logger;
#8 = Class #38 // com/redisc/Run
#9 = Class #39 // java/lang/Object
#10 = Utf8 log
#11 = Utf8 Lorg/slf4j/Logger;
#12 = Utf8 <init>
#13 = Utf8 ()V
#14 = Utf8 Code
#15 = Utf8 LineNumberTable
#16 = Utf8 LocalVariableTable
#17 = Utf8 this
#18 = Utf8 Lcom/redisc/Run;
#19 = Utf8 main
#20 = Utf8 ([Ljava/lang/String;)V
#21 = Utf8 args
#22 = Utf8 [Ljava/lang/String;
#23 = Utf8 s1
#24 = Utf8 Ljava/lang/String;
#25 = Utf8 s2
#26 = Utf8 s3
#27 = Utf8 <clinit>
#28 = Utf8 SourceFile
#29 = Utf8 Run.java
#30 = NameAndType #12:#13 // "<init>":()V
#31 = Utf8 a
#32 = Utf8 b
#33 = Utf8 ab
#34 = Utf8 c.Run
#35 = Class #40 // org/slf4j/LoggerFactory
#36 = NameAndType #41:#42 // getLogger:(Ljava/lang/String;)Lorg/slf4j/Logger;
#37 = NameAndType #10:#11 // log:Lorg/slf4j/Logger;
#38 = Utf8 com/redisc/Run
#39 = Utf8 java/lang/Object
#40 = Utf8 org/slf4j/LoggerFactory
#41 = Utf8 getLogger
#42 = Utf8 (Ljava/lang/String;)Lorg/slf4j/Logger;
{
public com.redisc.Run();
descriptor: ()V
flags: (0x0001) 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 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 // String a
2: astore_1
3: ldc #3 // String b
5: astore_2
6: ldc #4 // String ab
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 #5 // String c.Run
2: invokestatic #6 // Method org/slf4j/LoggerFactory.getLogger:(Ljava/lang/String;)Lorg/slf4j/Logger;
5: putstatic #7 // Field log:Lorg/slf4j/Logger;
8: return
LineNumberTable:
line 10: 0
}
SourceFile: "Run.java"

说一下,运行时候发生了什么。

  • StringTable 开始的时候是空的,[]
  • 常量池中的信息,都会被加载到运行时常量池中
    • 这时,abab 都是常量池中的符号,还没有变为 java 字符串对象「懒惰行为」
  • 当用到 a 的时候,也就是 ldc #2「在反编译中」 会把 a 符号变为 a 字符串
    • 这个时候,才会向 StringTable 添加 a
    • 此时 StringTable["a"]
  • 其他 bab 都是如此,只有用到时,才会添加进 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; // 经过反编译这段代码实际上为 new StringBuilder().append("a").append("b").toString()

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: #12 // com/redisc/Run
super_class: #13 // java/lang/Object
interfaces: 0, fields: 1, methods: 3, attributes: 1
Constant pool:
#1 = Methodref #13.#36 // java/lang/Object."<init>":()V
#2 = String #37 // a
#3 = String #38 // b
#4 = String #39 // ab
#5 = Class #40 // java/lang/StringBuilder
#6 = Methodref #5.#36 // java/lang/StringBuilder."<init>":()V
#7 = Methodref #5.#41 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#8 = Methodref #5.#42 // java/lang/StringBuilder.toString:()Ljava/lang/String;
#9 = String #43 // c.Run
#10 = Methodref #44.#45 // org/slf4j/LoggerFactory.getLogger:(Ljava/lang/String;)Lorg/slf4j/Logger;
#11 = Fieldref #12.#46 // com/redisc/Run.log:Lorg/slf4j/Logger;
#12 = Class #47 // com/redisc/Run
#13 = Class #48 // java/lang/Object
#14 = Utf8 log
#15 = Utf8 Lorg/slf4j/Logger;
#16 = Utf8 <init>
#17 = Utf8 ()V
#18 = Utf8 Code
#19 = Utf8 LineNumberTable
#20 = Utf8 LocalVariableTable
#21 = Utf8 this
#22 = Utf8 Lcom/redisc/Run;
#23 = Utf8 main
#24 = Utf8 ([Ljava/lang/String;)V
#25 = Utf8 args
#26 = Utf8 [Ljava/lang/String;
#27 = Utf8 s1
#28 = Utf8 Ljava/lang/String;
#29 = Utf8 s2
#30 = Utf8 s3
#31 = Utf8 s4
#32 = Utf8 s5
#33 = Utf8 <clinit>
#34 = Utf8 SourceFile
#35 = Utf8 Run.java
#36 = NameAndType #16:#17 // "<init>":()V
#37 = Utf8 a
#38 = Utf8 b
#39 = Utf8 ab
#40 = Utf8 java/lang/StringBuilder
#41 = NameAndType #49:#50 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#42 = NameAndType #51:#52 // toString:()Ljava/lang/String;
#43 = Utf8 c.Run
#44 = Class #53 // org/slf4j/LoggerFactory
#45 = NameAndType #54:#55 // getLogger:(Ljava/lang/String;)Lorg/slf4j/Logger;
#46 = NameAndType #14:#15 // log:Lorg/slf4j/Logger;
#47 = Utf8 com/redisc/Run
#48 = Utf8 java/lang/Object
#49 = Utf8 append
#50 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;
#51 = Utf8 toString
#52 = Utf8 ()Ljava/lang/String;
#53 = Utf8 org/slf4j/LoggerFactory
#54 = Utf8 getLogger
#55 = Utf8 (Ljava/lang/String;)Lorg/slf4j/Logger;
{
public com.redisc.Run();
descriptor: ()V
flags: (0x0001) 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 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 // String a
2: astore_1
3: ldc #3 // String b
5: astore_2
6: ldc #4 // String ab
8: astore_3
9: new #5 // class java/lang/StringBuilder
12: dup
13: invokespecial #6 // Method java/lang/StringBuilder."<init>":()V
16: aload_1
17: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
20: aload_2
21: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
24: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
27: astore 4
29: ldc #4 // String ab
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 #9 // String c.Run
2: invokestatic #10 // Method org/slf4j/LoggerFactory.getLogger:(Ljava/lang/String;)Lorg/slf4j/Logger;
5: putstatic #11 // Field log:Lorg/slf4j/Logger;
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 // String a
2: astore_1
3: ldc #3 // String b
5: astore_2
6: ldc #4 // String ab
8: astore_3
9: new #5 // class java/lang/StringBuilder
12: dup
13: invokespecial #6 // Method java/lang/StringBuilder."<init>":()V
16: aload_1
17: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
20: aload_2
21: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
24: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
27: astore 4
29: ldc #4 // String ab
31: astore 5
33: return

可以看出

  • 29: ldc #4 // String ab
    • 是直接去串池找的 ab,并且其执行的是 #4 指令

6 号位置是一样的。

  • 6: ldc #4 // String ab

所以,29 行代码是直接从常量池中拿到的 ab,所以,s3s5 是一个对象。

这是 javac 的一个编译优化。因为,javac 认为 s3s5 都是常量,不应该变化了。

请我喝杯咖啡吧~