前言

String是java常用的数据类型之一,我们知道String的对象可能存在于堆区和常量池,然而什么时候会在堆区创建对象,什么时候会取常量池中的对象,这是本文要讨论的问题。

正文

本节通过探索不同的String赋值方式,来讨论jvm如何使用堆区和常量区来创建String对象。

我们使用hotSpot虚拟机,版本为1.8.0_72,使用javap命令对class文件进行反汇编,探索对象创建过程

test1
String a = "hello";

对上述代码进行反汇编为:

Java如何创建字符串常量类 java创建字符串对象_反汇编

其中ldc(load Constant)指令,表示由常量区获取对象,然后将引用存储在astore_1。在这个过程中,只是在常量池创建了常量值,并未在堆区创建对象。

test2
String a = "hello";
String b = a;

对上述代码进行反汇编为:

Java如何创建字符串常量类 java创建字符串对象_反汇编_02

如上所示,对象a直接赋值给对象b,因此这两个对象地址相等。

test3
String c = new String("hello");

对上述代码进行反汇编为:

Java如何创建字符串常量类 java创建字符串对象_反汇编_03

如上所示,先在堆区创建String对象,然后再常量池创建了常量值,最后使用该常量值初始化了堆区的String对象。对象c使用的是堆中创建的对象。

test4
String a = “hello”;
String d = a + a;

对上述代码进行反汇编为:

Java如何创建字符串常量类 java创建字符串对象_java 创建string对象_04

在创建对象d时,先新建了一个StringBuilder对象,使用对象a的值进行初始化,然后再调用append方法再添加了一次a的值。最后使用toString方法反回一个String对象。

test5
String e = “hello” + “hello”;

对上述代码进行反汇编为:

Java如何创建字符串常量类 java创建字符串对象_创建对象_05

如上所示,现在常量池创建常量hellohello,然后e存储该引用。整个过程中,只在常量池创建了一个对象。

test6
String a = “hello”;
String f = a + “hello”;

对上述代码进行反汇编为:

Java如何创建字符串常量类 java创建字符串对象_java 创建string对象_06

在创建对象f时,先创建了StringBuilder对象,然后使用对象a的值用于初始化StringBuilder,然后调用append方法添加常量池的"hello"值。最后调用toString方法向对象f赋值。

test7
final String a = "hello";
String g = a + "hello";

对上述代码进行反汇编为:

Java如何创建字符串常量类 java创建字符串对象_堆区_07

如上所示,使用final修饰a后,a将作为常量构造g,而不需要再创建StringBuilder对象。

总结

只使用常量对对象进行赋值,只会在常量去创建对象;

使用new关键字创建对象,会在常量区和堆区都创建对象,并且引用为堆区对象;

使用变量对对象进行赋值,会先创建StringBuilder对象,再用append方法添加其他变量,最后使用toString方法创建String对象;

使用final修饰String对象后,该对象将被视为常量。