讲一讲Java的字符串常量池,看完你的思路就清晰了
前言
很多朋友Java的字符串常量池的概念困扰了很长一段时间,最近研究了一下jvm指令码,终于对它有了大概的了解。 在展示案例前,我们需要先搞清楚一个概念,众所周知,jvm的内存模型由程序计数器、虚拟机栈、本地方法栈、堆、元空间(方法区)、直接内存组成。 今天我们谈到的概念只和虚拟机栈、堆、元空间(方法区)有关。 先举个例子说明两种关于字符串最基本的使用情况:
-
String s =“abc”;在编译期间,会将等号右边的“abc”常量放在常量池中,在程序运行时,会将s变量压栈,栈中s变量直接指向元空间的字符串常量池abc项,没有经过堆内存。
-
String s = new String(“abc”);在编译期间,会将等号右边的“abc”常量放在常量池中,在程序运行时,先在堆中创建一个String对象,该对象的内容指向常量池的“abc”项。然后将s变量压栈,栈中s变量指向堆中的String对象。
下面通过javap -v xxx.class命令来查看class文件的指令码,通过分析这些指令码更确切的了解我们想知道的问题。笔者的jdk是1.8版本的,版本不同可能效果也不同。
第一个例子:String a = “abc”
首先展示我们的源代码
public class StringCodeTest {
public static void main(String[] args) {
String a = "abc";
System.out.println(a);
}
}
我们执行 javap -v StringCodeTest .class>StringCodeTest.txt命令,将指令码放到StringCodeTest.txt中。
Classfile /C:/Users/zhiyi/IdeaProjects/springboottest/target/classes/cn/lizy/service/StringCodeTest.class
Last modified 2020-11-7; size 610 bytes
MD5 checksum 2adeba87a0f1b315019efe540bb058cd
Compiled from "StringCodeTest.java"
public class cn.lizy.service.StringCodeTest
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #6.#22 // java/lang/Object."<init>":()V
#2 = String #23 // abc
#3 = Fieldref #24.#25 // java/lang/System.out:Ljava/io/PrintStream;
#4 = Methodref #26.#27 // java/io/PrintStream.println:(Ljava/lang/String;)V
#5 = Class #28 // cn/lizy/service/StringCodeTest
#6 = Class #29 // java/lang/Object
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 Lcn/lizy/service/StringCodeTest;
#14 = Utf8 main
#15 = Utf8 ([Ljava/lang/String;)V
#16 = Utf8 args
#17 = Utf8 [Ljava/lang/String;
#18 = Utf8 a
#19 = Utf8 Ljava/lang/String;
#20 = Utf8 SourceFile
#21 = Utf8 StringCodeTest.java
#22 = NameAndType #7:#8 // "<init>":()V
#23 = Utf8 abc
#24 = Class #30 // java/lang/System
#25 = NameAndType #31:#32 // out:Ljava/io/PrintStream;
#26 = Class #33 // java/io/PrintStream
#27 = NameAndType #34:#35 // println:(Ljava/lang/String;)V
#28 = Utf8 cn/lizy/service/StringCodeTest
#29 = Utf8 java/lang/Object
#30 = Utf8 java/lang/System
#31 = Utf8 out
#32 = Utf8 Ljava/io/PrintStream;
#33 = Utf8 java/io/PrintStream
#34 = Utf8 println
#35 = Utf8 (Ljava/lang/String;)V
{
public cn.lizy.service.StringCodeTest();
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 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcn/lizy/service/StringCodeTest;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=1
0: ldc #2 // String abc
2: astore_1
3: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
6: aload_1
7: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
10: return
LineNumberTable:
line 5: 0
line 6: 3
line 24: 10
LocalVariableTable:
Start Length Slot Name Signature
0 11 0 args [Ljava/lang/String;
3 8 1 a Ljava/lang/String;
}
SourceFile: "StringCodeTest.java"
关键要看Constant pool:(这个就是常量池)和main方法。
main方法中ldc #2 的意思是把常量池中的#2项压入到栈,而#2关联的#23就是“abc”常量。这就说明栈中的对象是直接指向了常量池的。String s = “abc”只生成了一个字符串对象(常量池中的对象)和一个栈中的引用
第二个例子 String s = new String(“abc”);
源代码
public class StringCodeTest {
public static void main(String[] args) {
String b = new String("abc");
System.out.println(b)
}
}
为了节省篇幅,第二个例子和第三个例子的jvm指令码就不再展示了,只截图关键点,有需要可以自己通过javap命令生成
main方法中先new #2, 而#2关联的#25就是String对象, 然后再ldc #3(关联的#26就是“abc”常量)也就是说,在执行时,先在堆中创建String对象,再把常量池中的#3项压入到栈(这里的栈应该是操作数栈,参考最上面的内存模型图)供String对象使用。所以,栈中的引用指向了堆中的String对象,String对象指向了常量池中的“abc”项,String s = new String(“abc”)生成了一个字符串对象(常量池中的对象)、一个堆中的String对象和一个栈中的引用。
第三个例子:String c = new String(“abc”)+“abc”;
源代码
public class StringCodeTest {
public static void main(String[] args) {
String c = new String("abc")+"abc";
System.out.println(c);
}
}
main方法中先创建了Stringbuiler对象,然后创建了String对象(就是new String("abc")), 再执行ldc #3将常量池中的字符串压入到栈。最后做计算,过程和第二个例子差不多,只不过多了个Stringbuiler的append操作。
总结:理解了大体的原理后,再遇到像 ==判断字符串相等,或者计算String s = new String(“abc”)再内存中创建了几个对象这样的问题时思路就清晰了
最后
感谢你看到这里,文章有什么不足还请指正,觉得文章对你有帮助的话记得给我点个赞,每天都会分享java相关技术文章或行业资讯,欢迎大家关注和转发文章!