讲一讲Java的字符串常量池,看完你的思路就清晰了

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相关技术文章或行业资讯,欢迎大家关注和转发文章!

以上是 讲一讲Java的字符串常量池,看完你的思路就清晰了 的全部内容, 来源链接: utcz.com/z/394829.html

回到顶部