首先看一下面试题
代码1
public static void main(String[] args) {
String s1 = "a";
String s2 = "b";
String s3 = "a" + "b";
String s4 = s1 + s2;
String s5 = "ab";
String s6 = s4.intern();
System.out.println(s3 == s4);
System.out.println(s3 == s5);
System.out.println(s3 == s6);
}
如果想回答这个问题,可以先分析如下代码的字节码指令
代码2
1 package com.example.demo;
2
3 /**
4 * @author micro.cloud.fly
5 * @date 2022/10/12 10:40 AM
6 * @desc
7 */
8 public class Stringtest01 {
9 public static void main(String[] args) {
10 String s1 = "a";
11 String s2 = "b";
12 String s3 = "ab";
13 }
14 }
通过javap命令,可以得到如下的字节码
javap -v Stringtest01.class
字节码如下:
Classfile /Users/java0904/IdeaProjects/jvm-study/target/classes/com/example/demo/Stringtest01.class
Last modified 2022年10月12日; size 552 bytes
MD5 checksum 56f169e3e3e64a5a7443e762cbef5534
Compiled from "Stringtest01.java"
public class com.example.demo.Stringtest01
minor version: 0
major version: 55
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #5 // com/example/demo/Stringtest01
super_class: #6 // java/lang/Object
interfaces: 0, fields: 0, methods: 2, attributes: 1
Constant pool:
#1 = Methodref #6.#25 // java/lang/Object."<init>":()V
#2 = String #26 // a
#3 = String #27 // b
#4 = String #28 // ab
#5 = Class #29 // com/example/demo/Stringtest01
#6 = Class #30 // java/lang/Object
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 Lcom/example/demo/Stringtest01;
#14 = Utf8 main
#15 = Utf8 ([Ljava/lang/String;)V
#16 = Utf8 args
#17 = Utf8 [Ljava/lang/String;
#18 = Utf8 s1
#19 = Utf8 Ljava/lang/String;
#20 = Utf8 s2
#21 = Utf8 s3
#22 = Utf8 MethodParameters
#23 = Utf8 SourceFile
#24 = Utf8 Stringtest01.java
#25 = NameAndType #7:#8 // "<init>":()V
#26 = Utf8 a
#27 = Utf8 b
#28 = Utf8 ab
#29 = Utf8 com/example/demo/Stringtest01
#30 = Utf8 java/lang/Object
{
public com.example.demo.Stringtest01();
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 8: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/example/demo/Stringtest01;
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 10: 0
line 11: 3
line 12: 6
line 13: 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;
MethodParameters:
Name Flags
args
}
SourceFile: "Stringtest01.java"
分析如下:
- Constant pool 是指常量池,常量池中的每一个**#num**代表一个常量,程序在运行的时候,这些数字会被翻译成真正的内存地址(逻辑地址或者物理地址)
- 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
- ldc:常量池中的常量值(intfloatstring reference,object reference)入栈
- astore_1 将栈顶引用类型值保存到局部变量1中
- 局部变量在这个表中间,可以看到局部变量123都是string类型,也就是对应着s1,s2,s3
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;
翻译:
LineNumberTable:
line 10: 0//文件的第10行,也就是第0步,常量池中的#2位置的常量值(a)(#号位置又引用了#26位置的常量a)入栈,
line 11: 3 同上
line 12: 6 同上
line 13: 9// 返回
常量池中的常量,都会被加载到运行时到常量池中,但是常量池中的a、b、ab只是符号,只有代码运行到10-12行的时候,这些符号,才会变为字符串对象。
分析代码1
通过反编译技术可以查看字节码文件如下:
Classfile /C:/Users/Administrator/IdeaProjects/vin/target/classes/com/example/vin/Stringtest02.class
Last modified 2022-10-12; size 1169 bytes
MD5 checksum 2ff3b898ef3cd1de122e3b1c66799088
Compiled from "Stringtest02.java"
public class com.example.vin.Stringtest02
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #13.#39 // java/lang/Object."<init>":()V
#2 = String #40 // a
#3 = String #41 // b
#4 = String #42 // ab
#5 = Class #43 // java/lang/StringBuilder
#6 = Methodref #5.#39 // java/lang/StringBuilder."<init>":()V
#7 = Methodref #5.#44 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#8 = Methodref #5.#45 // java/lang/StringBuilder.toString:()Ljava/lang/String;
#9 = Methodref #46.#47 // java/lang/String.intern:()Ljava/lang/String;
#10 = Fieldref #48.#49 // java/lang/System.out:Ljava/io/PrintStream;
#11 = Methodref #50.#51 // java/io/PrintStream.println:(Z)V
#12 = Class #52 // com/example/vin/Stringtest02
#13 = Class #53 // java/lang/Object
#14 = Utf8 <init>
#15 = Utf8 ()V
#16 = Utf8 Code
#17 = Utf8 LineNumberTable
#18 = Utf8 LocalVariableTable
#19 = Utf8 this
#20 = Utf8 Lcom/example/vin/Stringtest02;
#21 = Utf8 main
#22 = Utf8 ([Ljava/lang/String;)V
#23 = Utf8 args
#24 = Utf8 [Ljava/lang/String;
#25 = Utf8 s1
#26 = Utf8 Ljava/lang/String;
#27 = Utf8 s2
#28 = Utf8 s3
#29 = Utf8 s4
#30 = Utf8 s5
#31 = Utf8 s6
#32 = Utf8 StackMapTable
#33 = Class #24 // "[Ljava/lang/String;"
#34 = Class #54 // java/lang/String
#35 = Class #55 // java/io/PrintStream
#36 = Utf8 MethodParameters
#37 = Utf8 SourceFile
#38 = Utf8 Stringtest02.java
#39 = NameAndType #14:#15 // "<init>":()V
#40 = Utf8 a
#41 = Utf8 b
#42 = Utf8 ab
#43 = Utf8 java/lang/StringBuilder
#44 = NameAndType #56:#57 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#45 = NameAndType #58:#59 // toString:()Ljava/lang/String;
#46 = Class #54 // java/lang/String
#47 = NameAndType #60:#59 // intern:()Ljava/lang/String;
#48 = Class #61 // java/lang/System
#49 = NameAndType #62:#63 // out:Ljava/io/PrintStream;
#50 = Class #55 // java/io/PrintStream
#51 = NameAndType #64:#65 // println:(Z)V
#52 = Utf8 com/example/vin/Stringtest02
#53 = Utf8 java/lang/Object
#54 = Utf8 java/lang/String
#55 = Utf8 java/io/PrintStream
#56 = Utf8 append
#57 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;
#58 = Utf8 toString
#59 = Utf8 ()Ljava/lang/String;
#60 = Utf8 intern
#61 = Utf8 java/lang/System
#62 = Utf8 out
#63 = Utf8 Ljava/io/PrintStream;
#64 = Utf8 println
#65 = Utf8 (Z)V
{
public com.example.vin.Stringtest02();
descriptor: ()V
flags: 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 8: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/example/vin/Stringtest02;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=7, 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: aload 4
35: invokevirtual #9 // Method java/lang/String.intern:()Ljava/lang/String;
38: astore 6
40: getstatic #10 // Field java/lang/System.out:Ljava/io/PrintStream;
43: aload_3
44: aload 4
46: if_acmpne 53
49: iconst_1
50: goto 54
53: iconst_0
54: invokevirtual #11 // Method java/io/PrintStream.println:(Z)V
57: getstatic #10 // Field java/lang/System.out:Ljava/io/PrintStream;
60: aload_3
61: aload 5
63: if_acmpne 70
66: iconst_1
67: goto 71
70: iconst_0
71: invokevirtual #11 // Method java/io/PrintStream.println:(Z)V
74: getstatic #10 // Field java/lang/System.out:Ljava/io/PrintStream;
77: aload_3
78: aload 6
80: if_acmpne 87
83: iconst_1
84: goto 88
87: iconst_0
88: invokevirtual #11 // Method java/io/PrintStream.println:(Z)V
91: return
LineNumberTable:
line 10: 0
line 11: 3
line 12: 6
line 13: 9
line 14: 29
line 15: 33
line 17: 40
line 18: 57
line 19: 74
line 20: 91
LocalVariableTable:
Start Length Slot Name Signature
0 92 0 args [Ljava/lang/String;
3 89 1 s1 Ljava/lang/String;
6 86 2 s2 Ljava/lang/String;
9 83 3 s3 Ljava/lang/String;
29 63 4 s4 Ljava/lang/String;
33 59 5 s5 Ljava/lang/String;
40 52 6 s6 Ljava/lang/String;
StackMapTable: number_of_entries = 6
frame_type = 255 /* full_frame */
offset_delta = 53
locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String, cl
ass java/lang/String ]
frame_type = 255 /* full_frame */
offset_delta = 0
locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String, cl
ass java/lang/String ]
stack = [ class java/io/PrintStream, int ]
MethodParameters:
Name Flags
args
}
SourceFile: "Stringtest02.java"
有一点需要说明,常量池中的这些符号,并不是对象,而是只有在运行时,常量池的这些符号会被加载到内存,运行到对应的代码后(如 string s1=“a”),再把这些符号变为对象,变为对象的时候,jvm会首先分配一个空间,叫做stringTable,用来存储这些字符串,如果stringTable中已经有这个字符串对象了,则不会另外再生成这个字符串对象。
s3==s4
通过 9: new #5 // class java/lang/StringBuilder 可以看到,在进行s4=s1+s2的时候,底层实际上是进行了调用stringbuilder的构造方法,那么很明显,会重新生成一个对象,因此s3==s4为false;
s3==s5
那么s3==s5呢,可以看到LineNumberTable中的第14行,对应的是Code的29,即
29: ldc #4 // String ab
可以看到直接加载了#4这个常量池的字符串常量,没有生成新的字符串对象,所以s3==s5为true。如果在String s5="ab"的下面加上一行
String s7 = "a"+"b";
此时,s3==s7的结果是什么呢,答案也为true,生成的字节码同上,原因是jvm在编译器进行了优化,直接认为s7的结果为确定的“ab”,因此,可以直接在字符串池中找到。
s3==s6
再看s3==s6呢,s6==s4.intern()方法,是尝试把s4的字符串对象放入到字符串常量池stringTable中,如果字符串常量池中已经有了,则不放入,如果没有,则放入,并且s4也会指向常量池中的字符串对象,(也有人说是字符串常量池中的对象是堆中的对象的引用,此处我没有深入研究)但是不论有没有,都会返回常量池中的字符串对象给s6,即s6来自于字符串常量池。所以s3 == s6
为true。