# JVM String.intern 与 StringTable - SymbolTable 原理

问题引入

以下代码为Java 字符串 最常见的问题,很多读者全靠背,对其中的原理一点都不清楚,导致容易出现问题,笔者在B站(https://www.bilibili.com/video/BV1iQ4y1U7qh?spm_id_from=333.999.0.0)中通过视频讲解了其中原理。但是还是有读者不能理解其中的精髓,好在我们在混沌学堂中,详细讲解了ELF文件的字符串表和符号表的原理,以其原理来打通StringTable与SymbolTable的原理非常之轻松,且愉快。在本文的背后,笔者再解释以下的结果为什么会是如此。

注:由于JDK 8 市场占有率较高,所以笔者以此版本来做分析,若读者需要研究高版本,可以根据本文提示来找到对应高版本入口自行研究。

public class Demo {

  public static void main(String[] args){

  String a = "abc";

  String b = "ab" + "c";

  String c = new String("abc");

  String d = new String("abce");

    String e = new String( a + b );

  System.out.println(a == a.intern()); // true

  System.out.println(a == b); // true

  System.out.println(b == b.intern()); // true

  System.out.println(b == c); // false

  System.out.println(c == c.intern()); // false

  System.out.println(d == d.intern()); // false

  System.out.println(e == e.intern()); // true

 }

}
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

字节码层面

“物有本末,事有终始,知其先后,则近道矣”,还记得笔者在混沌学堂说过的话么:研究底层原理就如同 jc 破案,掌握所有证据链、动机、手法、人证、物证,所以我们需要先看看字节码层面到底干了什么,然后再深入到JVM源码,看看字节码又干了什么,于是乎,真相浮出水面~。

咳咳,字节码貌似有点长,不过不打紧,因为都是重复的字节码,关键的字节码也就:ldc 字节码而已,该字节码用于解析字符串引用,比如 ldc #2,表示将 2 号常量池 也即 String 常量池中的 abc 引用放到操作数栈顶(额,这里假设 读者 知道 Java 栈帧包含:局部变量表、操作数栈、指向常量池指针~)。

通过字节码我们得知如下信息:

  1. Java 栈帧的局部变量表中,1 slot 存放 "abc" 的引用 这里 变量为 a
  2. Java 栈帧的局部变量表中,2 slot 存放 "abc" 的引用 这里 变量为 b(编译器将:"ab" + "c" 优化为 "abc")
  3. Java 栈帧的局部变量表中,3 slot 存放 new String("abc") 的引用 这里 变量为 c
  4. Java 栈帧的局部变量表中,4 slot 存放 new String("abce") 的引用 这里 变量为 d
  5. Java 栈帧的局部变量表中,5 slot 存放 new String( a + b ) 的引用 这里 变量为 e(编译器通将:a 和 b 字符串引用 通过 StringBuilder 的 append 方法 进行封装,所以这里实际存储为 StringBuilder 的引用 )
  6. 在常量池中:String 常量池 中 指向 utf8 常量池 ,而对于 utf8 常量池 来说,其中就保留了 对应的字面量信息。如:#2 = String #28 , #28 = Utf8 abc 其他 字面量依旧如此(有没有发现:ELF 中的 符号表持有 字符串表的下标,字符串表 包含 实际编码的字符串信息,详细 参考 混沌学堂 ELF 系列)
 Constant pool:

 #1 = Methodref     #14.#27    // java/lang/Object."<init>":()V

 #2 = String      #28      // abc

 #3 = Class       #29      // java/lang/String

 #4 = Methodref     #3.#30    // java/lang/String."<init>":(Ljava/lang/String;)V

 #5 = String      #31      // abce

 #6 = Class       #32      // java/lang/StringBuilder

 #7 = Methodref     #6.#27    // java/lang/StringBuilder."<init>":()V

 #8 = Methodref     #6.#33    // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

 #9 = Methodref     #6.#34    // java/lang/StringBuilder.toString:()Ljava/lang/String;

 #10 = Fieldref     #35.#36    // java/lang/System.out:Ljava/io/PrintStream;

 #11 = Methodref     #3.#37    // java/lang/String.intern:()Ljava/lang/String;

 #12 = Methodref     #38.#39    // java/io/PrintStream.println:(Z)V

 #13 = Class       #40      // Demo

 #14 = Class       #41      // java/lang/Object

 #15 = Utf8       <init>

 #16 = Utf8       ()V

 #17 = Utf8       Code

 #18 = Utf8       LineNumberTable

 #19 = Utf8       main

 #20 = Utf8       ([Ljava/lang/String;)V

 #21 = Utf8       StackMapTable

 #22 = Class       #42      // "[Ljava/lang/String;"

 #23 = Class       #29      // java/lang/String

 #24 = Class       #43      // java/io/PrintStream

 #25 = Utf8       SourceFile

 #26 = Utf8       demo.java

 #27 = NameAndType    #15:#16    // "<init>":()V

 #28 = Utf8       abc

 #29 = Utf8       java/lang/String

 #30 = NameAndType    #15:#44    // "<init>":(Ljava/lang/String;)V

 #31 = Utf8       abce

 #32 = Utf8       java/lang/StringBuilder

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

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

 #35 = Class       #49      // java/lang/System

 #36 = NameAndType    #50:#51    // out:Ljava/io/PrintStream;

 #37 = NameAndType    #52:#48    // intern:()Ljava/lang/String;

 #38 = Class       #43      // java/io/PrintStream

 #39 = NameAndType    #53:#54    // println:(Z)V

 #40 = Utf8       Demo

 #41 = Utf8       java/lang/Object

 #42 = Utf8       [Ljava/lang/String;

 #43 = Utf8       java/io/PrintStream

 #44 = Utf8       (Ljava/lang/String;)V

 #45 = Utf8       append

 #46 = Utf8       (Ljava/lang/String;)Ljava/lang/StringBuilder;

 #47 = Utf8       toString

 #48 = Utf8       ()Ljava/lang/String;

 #49 = Utf8       java/lang/System

 #50 = Utf8       out

 #51 = Utf8       Ljava/io/PrintStream;

 #52 = Utf8       intern

 #53 = Utf8       println

 #54 = Utf8       (Z)V
public static void main(java.lang.String[]);

  descriptor: ([Ljava/lang/String;)V

  flags: ACC_PUBLIC, ACC_STATIC

  Code:

   stack=4, locals=6, args_size=1

    0: ldc     #2         // String abc

    2: astore_1  // 对应代码:String a = "abc";

    3: ldc     #2         // String abc

    5: astore_2  // 对应代码:String b = "ab" + "c"; 

    6: new     #3         // class java/lang/String

    9: dup

    10: ldc     #2         // String abc

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

    15: astore_3  // 对应代码:String c = new String("abc"); 

    16: new     #3         // class java/lang/String

    19: dup

    20: ldc     #5         // String abce

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

    25: astore    4 // 对应代码:String d = new String("abce");

    27: new     #3         // class java/lang/String

    30: dup

    31: new     #6         // class java/lang/StringBuilder

    34: dup

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

    38: aload_1

    39: invokevirtual #8         // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

    42: aload_2

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

    46: invokevirtual #9         // Method java/lang/StringBuilder.toString:()Ljava/lang/String;

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

    52: astore    5 // 对应代码:String e = new String( a + b ); 

    54: getstatic  #10        // Field java/lang/System.out:Ljava/io/PrintStream;

    57: aload_1

    58: aload_1

    59: invokevirtual #11        // Method java/lang/String.intern:()Ljava/lang/String;

    62: if_acmpne  69

    65: iconst_1

    66: goto     70

    69: iconst_0

    70: invokevirtual #12        // Method java/io/PrintStream.println:(Z)V

    73: getstatic  #10        // Field java/lang/System.out:Ljava/io/PrintStream;

    76: aload_1

    77: aload_2

    78: if_acmpne  85

    81: iconst_1

    82: goto     86

    85: iconst_0

    86: invokevirtual #12        // Method java/io/PrintStream.println:(Z)V

    89: getstatic  #10        // Field java/lang/System.out:Ljava/io/PrintStream;

    92: aload_2

    93: aload_2

    94: invokevirtual #11        // Method java/lang/String.intern:()Ljava/lang/String;

    97: if_acmpne  104

   100: iconst_1

   101: goto     105

   104: iconst_0

   105: invokevirtual #12        // Method java/io/PrintStream.println:(Z)V

   108: getstatic  #10        // Field java/lang/System.out:Ljava/io/PrintStream;

   111: aload_2

   112: aload_3

   113: if_acmpne  120

   116: iconst_1

   117: goto     121

   120: iconst_0

   121: invokevirtual #12        // Method java/io/PrintStream.println:(Z)V

   124: getstatic  #10        // Field java/lang/System.out:Ljava/io/PrintStream;

   127: aload_3

   128: aload_3

   129: invokevirtual #11        // Method java/lang/String.intern:()Ljava/lang/String;

   132: if_acmpne  139

   135: iconst_1

   136: goto     140

   139: iconst_0

   140: invokevirtual #12        // Method java/io/PrintStream.println:(Z)V

   143: getstatic  #10        // Field java/lang/System.out:Ljava/io/PrintStream;

   146: aload    4

   148: aload    4

   150: invokevirtual #11        // Method java/lang/String.intern:()Ljava/lang/String;

   153: if_acmpne  160

   156: iconst_1

   157: goto     161

   160: iconst_0

   161: invokevirtual #12        // Method java/io/PrintStream.println:(Z)V

   164: getstatic  #10        // Field java/lang/System.out:Ljava/io/PrintStream;

   167: aload    5

   169: aload    5

   171: invokevirtual #11        // Method java/lang/String.intern:()Ljava/lang/String;

   174: if_acmpne  181

   177: iconst_1

   178: goto     182

   181: iconst_0

   182: invokevirtual #12        // Method java/io/PrintStream.println:(Z)V

   185: return

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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295

JVM 层面

那么,了解字节码后,直接上 JVM 源码,由于源码涉及层面较多,笔者这里删减部分不相关代码,直接看真相。

类加载常量池解析原理

对于常量池而言,将会在类加载的时候进行解析,所以我们先看解析过程,毕竟class文件始终只是静态数据,在JVM中肯定需要加载放到指定位置等待使用(再次强调:不要关注C++细节哈,当Java看,看不懂 的话,看注释)。通过源码我们得知如下信息:

  1. 对于 JVM_CONSTANT_String 字符串常量池,将会在ConstantPool 常量池对象中保存 字符串常量池下标与 utf 8 的映射 通过 string_index_at_put 方法
  2. 对于 JVM_CONSTANT_Utf8 字符串常量池,将会保存 _cp->symbol_at_put(index, result) utf8 常量池下标与Symbol 引用的映射,而字面量最终将会被添加到 SymbolTable 中
instanceKlassHandle ClassFileParser::parseClassFile(Symbol* name,

                          ClassLoaderData* loader_data,

                          Handle protection_domain,

                          KlassHandle host_klass,

                          GrowableArray<Handle>* cp_patches,

                          TempNewSymbol& parsed_name,

                          bool verify,

                          TRAPS) {

  // Constant pool 直接看这里

  constantPoolHandle cp = parse_constant_pool(CHECK_(nullHandle));

}

​

constantPoolHandle ClassFileParser::parse_constant_pool(TRAPS) {

  // parsing constant pool entries 转换每一个 #1 - #N 的常量池

  parse_constant_pool_entries(length, CHECK_(nullHandle));

}// 由于常量池类型较多,我们这里只涉及到:String 与 Utf 8 就可以

void ClassFileParser::parse_constant_pool_entries(int length, TRAPS) {

  // parsing Index 0 is unused 开始转换,0 下标 将不使用,也即常量池将从 #1 开始解析

  for (int index = 1; index < length; index++) {

    u1 tag = cfs->get_u1_fast(); // 获取一个字节的常量池标签(也即:String 或者 Utf 8)

    switch (tag) {

      case JVM_CONSTANT_String :

       {

          cfs->guarantee_more(3, CHECK); // 保证后面字节存在 string_index, tag/access_flags

          u2 string_index = cfs->get_u2_fast(); // 获取指向 utf8 的下标

          _cp->string_index_at_put(index, string_index); // 将当前常量池下标与指向utf8 常量池的下标保存到ConstantPool 类中(在本示例中,也即保存:2 和 28,其他的字面量依旧如此 )

       }

        break;

      case JVM_CONSTANT_Utf8 :

       {

          cfs->guarantee_more(2, CHECK); // 保证后面字节存在 utf8_length 也即字符串长度

          u2 utf8_length = cfs->get_u2_fast(); // 获取 2 字节长度

          u1* utf8_buffer = cfs->get_u1_buffer(); // 获取字符串缓冲区(这里其实就是跟在2字节长度后面的真实字符串的字符指针,也即:length + content)

          cfs->skip_u1_fast(utf8_length); // 让当前流指针掠过utf8字符串,指向下一个常量池

          unsigned int hash;

          Symbol* result = SymbolTable::lookup_only((char*)utf8_buffer, utf8_length, hash); // 看看SymbolTable表中是否存在该 字面量 , 若不存在,那么将该字面量放入 SymbolTable 中

          if (result == NULL) {

            names[names_count] = (char*)utf8_buffer;

            lengths[names_count] = utf8_length;

            indices[names_count] = index;

            hashValues[names_count++] = hash;

            if (names_count == SymbolTable::symbol_alloc_batch_size) {

              SymbolTable::new_symbols(_loader_data, _cp, names_count, names, lengths, indices, hashValues, CHECK); // 将字面量保存到 SymbolTable 中,而 _cp->symbol_at_put 将在其中完成映射

              names_count = 0;

           }

         } else { // 若字面量已经存在,那么保存 当前 utf8 下标 和 Symbol的引用映射

            _cp->symbol_at_put(index, result);

         }

       }

        break;  

   }

 }

}// 常量池对象,用于保存所有常量池信息

class ConstantPool : public Metadata {// which 常量池下标,string_index utf8 下标

  void string_index_at_put(int which, int string_index) {

    tag_at_put(which, JVM_CONSTANT_StringIndex); // 在which下标处存放 JVM_CONSTANT_StringIndex,将String常量池转为 JVM_CONSTANT_StringIndex 下标类型的常量池

    *int_at_addr(which) = string_index; // 将 utf8 下标放入对象末尾的空间中,下标同样为 which

 }// which 常量池下标,s 为符号引用

  void symbol_at_put(int which, Symbol* s) {

    tag_at_put(which, JVM_CONSTANT_Utf8); // 在which下标处存放 JVM_CONSTANT_Utf8

    *symbol_at_addr(which) = s; // 将Symbol地址信息保存到数组下标为which处

 }

​

  Array<u1>*     _tags;    // tag数组,保存常量池内容信息,每个数组单元 1 字节

  void tag_at_put(int which, jbyte t)    { tags()->at_put(which, t); } // 在对应数组下标为which处放入存放t

  

  // 获取当前ConstantPool对象末尾内存的 intptr_t 指针(也即该内存解释为:intptr_t 数组,在不同平台上该值将随之不同,比如在 64 为中,该值为 64位)

  intptr_t* base() const { return (intptr_t*) (((char*) this) + sizeof(ConstantPool)); }

  

  // 获取当前 ConstantPool 类 内存末尾地址为which处的指针,比如: ConstantPool 占用 8byte,那么这里的 &base() 就等于获取末尾8byte的指针,而该对象内存末尾将用于存放 常量池对应的数据信息(末尾为 intptr_t 数组)~ 

  jint* int_at_addr(int which) const {

    return (jint*) &base()[which];

 }// 获取ConstantPool对象 末尾下标为which处的地址,并将其转为 Symbol** ,也即二级指针(注意:在混沌学堂说的,指针类型为内存空间的解释,这里将其解释为:保存一个指针的地址!)

  Symbol** symbol_at_addr(int which) const {

    return (Symbol**) &base()[which];

 }}
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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175

ldc 字节码原理

我们从上面的分析了解到,ConstantPool对象的 Array* _tags 保存了常量池的类型,ConstantPool对象的末尾保存了常量池类型对应的数据,而对于UTF8而言,Symbol 引用保存在其中。那么接下来我们来看看ldc字节码的原理,看看ldc如何获取到字符串的引用信息。

CASE(_ldc):{

  u2 index = pc[1]; // 获取ldc字节码后面指向常量池的下标

  ConstantPool* constants = METHOD->constants(); // 获取常量池引用

  switch (constants->tag_at(index).value()) { // 获取当前指向index处的常量池类型,也即tag数组对应下标处的类型,这里我们只需要看String即可(有读者可能会问:上面个不是保存的是:JVM_CONSTANT_StringIndex么,为啥这里是String,是这样的,那个值只是个临时值,在解析完常量池entry后,将会将其修改为JVM_CONSTANT_String,同时在对应下标处将会将其修改为JVM_CONSTANT_String,同时在ConstantPool对象末尾处的值将不再是utf8的下标,将会修改为Symbol的引用~学过ELF的道友,应该知道,这就是符号解析~)

    case JVM_CONSTANT_String:

     {

        oop result = constants->resolved_references()->obj_at(index); // 获取到已经解析好的引用,不过我们这里压根没解析,只有Symbol引用(可能有读者不理解oop是什么,这里只需要知道:它就是Java的String对象引用就好了)

        if (result == NULL) { // 首次调用,结果必然为空,所以进行解析(读者想想:是不是解析完了以后再次调用index下标的oop,就直接从resolved_references 中获取即可?)

          CALL_VM(InterpreterRuntime::resolve_ldc(THREAD, (Bytecodes::Code) opcode), handle_exception);

          SET_STACK_OBJECT(THREAD->vm_result(), 0); // 解析完成后将对象压入操作数栈中

          THREAD->set_vm_result(NULL);

       } else { // 结果不为空,直接放入操作数栈即可

          SET_STACK_OBJECT(result, 0);

       }

        break;

     }

 }

}// 接下来我们来看解析过程

IRT_ENTRY(void, InterpreterRuntime::resolve_ldc(JavaThread* thread, Bytecodes::Code bytecode)) {

  Bytecode_loadconstant ldc(m, bci(thread));

  oop result = ldc.resolve_constant(CHECK); // 直接看这里

}

IRT_END

​

  oop Bytecode_loadconstant::resolve_constant(TRAPS) const {

  int index = raw_index();

  ConstantPool* constants = _method->constants();

  if (has_cache_index()) { // 表示是否缓存,无伤大雅,看下面的else即可

    return constants->resolve_cached_constant_at(index, THREAD);

 } else { // 我们看这里

    return constants->resolve_constant_at(index, THREAD); 

 }

}

​

oop resolve_constant_at(int index, TRAPS) {

  constantPoolHandle h_this(THREAD, this);

  return resolve_constant_at_impl(h_this, index, _no_index_sentinel, THREAD);

}

​

oop ConstantPool::resolve_constant_at_impl(constantPoolHandle this_oop, int index, int cache_index, TRAPS) {

  oop result_oop = NULL;

  Handle throw_exception;

  int tag_value = this_oop->tag_at(index).value(); // 获取tag 标签 index 下标处的常量池类型,这里我们只需要关注:JVM_CONSTANT_String

  switch (tag_value) {

    case JVM_CONSTANT_String:

      result_oop = string_at_impl(this_oop, index, cache_index, CHECK_NULL); // 看这里,通过string_at_impl方法 拿到了 Java的String 对象 oop

      break;

 }

  if (cache_index >= 0) { // 若指定缓存解析后的对象,那么在resolved_references下标处保存即可

    Handle result_handle(THREAD, result_oop);

    oop result = this_oop->resolved_references()->obj_at(cache_index);

    if (result == NULL) {

      this_oop->resolved_references()->obj_at_put(cache_index, result_handle());

      return result_handle();

   } else {

      return result;

   }

 } else { // 否则直接返回

    return result_oop;

 }

}// 实际解析过程

oop ConstantPool::string_at_impl(constantPoolHandle this_oop, int which, int obj_index, TRAPS) {

  // 先在resolved_references中尝试获取,若已经解析,那么直接返回

  oop str = this_oop->resolved_references()->obj_at(obj_index);

  if (str != NULL) return str;

  // 否则,获取Symbol引用,然后调用StringTable::intern方法,将符号信息进行解析,然后生成oop,最后将oop 通过string_at_put方法放入到resolved_references数组中即可

  Symbol* sym = this_oop->unresolved_string_at(which);

  str = StringTable::intern(sym, CHECK_(NULL));

  this_oop->string_at_put(which, obj_index, str);

  return str;

}void string_at_put(int which, int obj_index, oop str) {

  resolved_references()->obj_at_put(obj_index, str);

}// 根据Symbol符号引用,生成String oop

oop StringTable::intern(Symbol* symbol, TRAPS) {

  if (symbol == NULL) return NULL;

  ResourceMark rm(THREAD);

  int length;

  // 获取经过unicode编码的字符数组

  jchar* chars = symbol->as_unicode(length);

  Handle string;

  oop result = intern(string, chars, length, CHECK_NULL); // 进行转换

  return result;

}// 转换操作

oop StringTable::intern(const char* utf8_string, TRAPS) {

  if (utf8_string == NULL) return NULL;

  ResourceMark rm(THREAD);

  int length = UTF8::unicode_length(utf8_string);

  jchar* chars = NEW_RESOURCE_ARRAY(jchar, length);

  UTF8::convert_to_unicode(utf8_string, chars, length); // 进行UTF8编码

  Handle string;

  oop result = intern(string, chars, length, CHECK_NULL); // 完成转换

  return result;

}// 最终转换操作

oop StringTable::intern(Handle string_or_null, jchar* name,

            int len, TRAPS) {

  unsigned int hashValue = hash_string(name, len);

  int index = the_table()->hash_to_index(hashValue);

  // 已经存在,那么直接返回

  oop found_string = the_table()->lookup(index, name, len, hashValue);

  if (found_string != NULL) return found_string;

  // 否则根据 jchar* name 与 len 长度创建String对象

  Handle string;

  if (!string_or_null.is_null()) {

    string = string_or_null;

 } else {

    string = java_lang_String::create_from_unicode(name, len, CHECK_NULL);// 该方法,笔者不再追踪,因为这里面就是很简单的:new String()操作了,只不过在JVM级别来调用,涉及到JVM的其他细节,这里省略并不会影响整体流程,只需要把这里当作 new String(byte[])即可

 }

  // 将对象添加到StringTable中即可

  return the_table()->basic_add(index, string, name, len,

                 hashValue, CHECK_NULL);

}
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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245

SymbolTable 与 StringTable

至此,我们可以从上面的字节码原理中看到:

  1. SymbolTable 保存 字面量 unicode 编码信息
  2. StringTable 保存 Java String oop 对象信息

最终解释

最后我们根据StringTable 与 SymbolTable 来解释如下结果:

  1. a == a.intern() : a字面量已经在String 常量池中存在,在ldc时解析生成了String对象,并放入了resolved_references,所以a与a.intern() 将返回同一个对象
  2. a == b :b已经被编译器优化成 abc 所以同 1所述
  3. b == b.intern() :同 1 所述
  4. b == c :c 我们看到首先是 ldc,获取到JVM生成的String,然后再次调用new String,将JVM生成的String放入其中,所以 为 false:两个对象都不是同一个
  5. c == c.intern() : 由于 "abc" 已经存在于StringTable中,所以c.intern()返回的是1 ldc放入到的对象,自然为false
  6. d == d.intern() :同 4 所述,"abce" 已经存在一个JVM 在 ldc时创建的 java 字符串对象
  7. e == e.intern() :该对象为动态 a + b 生成,在StringTable中不存在对应向,所以这里intern 时将 e 对象保存在了StringTable中,所以返回为 e对象,自然为 true
public class Demo {

  public static void main(String[] args){

    String a = "abc";

    String b = "ab" + "c";

    String c = new String("abc");

    String d = new String("abce");

    String e = new String( a + b );

    System.out.println(a == a.intern()); // true

    System.out.println(a == b); // true

    System.out.println(b == b.intern()); // true

    System.out.println(b == c); // false

    System.out.println(c == c.intern()); // false

    System.out.println(d == d.intern()); // false

    System.out.println(e == e.intern()); // true

 }

}
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

JVM的StringTable 与 SymbolTable设计,与 ELF 的 StringTable 和 SymbolTable一样,都是将字面量编码保存与实际字符元数据分离,这样可以让不同元数据共享同一个字面量编码~,只不过在ELF中StringTable 保存字面量,而SymbolTable保存索引下标,在JVM中恰好相反~表现了自己的不同~,就是颠倒下语义和单词。

最后:基础不牢地动山摇~,与其在上层玩,不如来底层专研,因为底层学通,上层自然通。

物有本末,事有终始,知其先后,则近道矣