字符串常量池(StringTable)
String的基本特性
-
String字符串,使用一对“”来表示
-
String声明为final类,不可被继承
-
String声明了
java.io.Serializable, Comparable<String>
接口,表明字符串支持序列化,支持大小比较。 -
String在jdk8即以前内部定义了
private final char value[]
。在jdk9开始内部定义的是private final byte value[]
。
说明:
因为String中存在很多字符只占一个byte,比如阿拉伯数字,但是一个char代表两个byte,所以浪费了空间。改成char[]节约了空间。 -
String具有不可变性,比如:
说明:
1.当对字符串重新赋值时,需要重新new一个obj,不能使用原来的value地址。
2.当对现有的字符串进行拼接操作时,也是同样new一个obj。
3.使用string的replace()也是同上。 -
通过字面量的方式给一个字符串赋值,此时的字符串值声明在字符串常量池中。eg:
String a="abc";
-
字符串常量池(String Table)不会存储相同的字符串。
String的内存分配
-
直接使用双引号声明的String对象会直接存储在String常量池中。eg:
String a="abc";
-
如果不是双引号声明的String对象,可以使用String的intern()方法。
-
jdk7及以后将StringTable放到了堆中。
字符串拼接操作
常量与常量的拼接结果在常量池,原理是编译器优化
//这里是源代码
public class Solution {
public static void main(String[] args) {
String str="abc"+"def";
}
}
这里是编译后的代码
常量池中不会存在相同类型的常量
解释
上面三幅图可以看出程序一开始里面有1306个String对象,但是这里声明了两个String,最后只有1307个,说明StringTable里只有一个“abc”
只要其中一个是变量,结果就在堆中。变量拼接的原理是StringBuilder
eg:
public class Solution {
public static void main(String[] args) {
String str="abc";
String str2=str+"edf";
}
}
以上代码块的字节码指令:
0 ldc #2 <abc>
2 astore_1
3 new #3 <java/lang/StringBuilder>
6 dup
7 invokespecial #4 <java/lang/StringBuilder.<init> : ()V>
10 aload_1
11 invokevirtual #5 <java/lang/StringBuilder.append : (Ljava/lang/String;)Ljava/lang/StringBuilder;>
14 ldc #6 <edf>
16 invokevirtual #5 <java/lang/StringBuilder.append : (Ljava/lang/String;)Ljava/lang/StringBuilder;>
19 invokevirtual #7 <java/lang/StringBuilder.toString : ()Ljava/lang/String;>
22 astore_2
23 return
从上面可以看出来str2这个字符串是先new StringBuilder()对象,然后执行两次append()方法,最后再通过toString()返回一个新的String对象。
如果拼接的结果调用intern()方法,则主动将常量池中还没有的字符串对象放入池中,并返回此对象地址。
解释:str存储的是再字符串常量池的abc的地址,str2存的是在堆空间的str2对象的地址,str3因为调用了intern()方法,所以回去字符串常量池找是否有abc的字符,因为有,所以直接返回在字符串常量池的abc的地址。
面试题
new String(“ab”)会创建几个对象?
创建两个,首先是在堆中new一个String对象,其次是在字符串常量池new一个ab对象。
new String(“a”)+new String(“b”)会创建几个对象?
创建6个,这里直接参考字节码指令
0 new #2 <java/lang/StringBuilder>
3 dup
4 invokespecial #3 <java/lang/StringBuilder.<init> : ()V>
7 new #4 <java/lang/String>
10 dup
11 ldc #5 <a>
13 invokespecial #6 <java/lang/String.<init> : (Ljava/lang/String;)V>
16 invokevirtual #7 <java/lang/StringBuilder.append : (Ljava/lang/String;)Ljava/lang/StringBuilder;>
19 new #4 <java/lang/String>
22 dup
23 ldc #8 <b>
25 invokespecial #6 <java/lang/String.<init> : (Ljava/lang/String;)V>
28 invokevirtual #7 <java/lang/StringBuilder.append : (Ljava/lang/String;)Ljava/lang/StringBuilder;>
31 invokevirtual #9 <java/lang/StringBuilder.toString : ()Ljava/lang/String;>
34 astore_1
35 return
解释:
1.创建一个StringBuilder对象->对应的字节码行号0
2.new String(“a”)->7
3.字符串常量池a->11
4.new String(“b”)->19
5.字符串常量池b->23
6.toString()方法调用会new 一个String(“ab”)对象->31
ps:toString()方法不会在字符串常量池new ab对象
intern的使用
一个字符串调用intern()方法,会先去字符串常量池查找是否有这个字符串,如果有则直接返回该地址,如果没有,则会把该字符串对象的地址复制一份放进常量池,这是字符串常量池指向该对象,而返回的引用虽然是字符串常量池的地址,其实返回的也是该对象的地址。
解释:
第一张图返回false,是因为执行的intern()方法会去常量池查找是否有ab,因为有,则返回该地址。后续比较的却还是str对象(在堆中)和str2(在字符串常量池中)。
第二张图返回true,是因为执行的intern()方法会去常量池查找是否有ab,因为没有,所以会把该字符串对象的地址复制一份放进常量池,这是字符串常量池指向该对象,而返回的引用虽然是字符串常量池的地址,其实返回的也是该对象的地址。所以字符串常量池中的ab的地址存储的就是堆中存储的对象ab的地址,二者相同,只不过是字符串常量池引用了堆。
StringTable的垃圾回收
StringTable中也存在垃圾回收。
G1的String去重操作
动机:许多大型Java应用程序目前都存在内存瓶颈。测量表明,在这些类型的应用程序中,大约25%的熔岩堆活动数据集被字符串对象使用。而且,这些String对象中大约有一半是重复的,其中重复表示string1。Equals (string2)为真。实际上,在堆上拥有重复的字符串对象只是在浪费内存。本项目将在G1垃圾收集器中实现自动连续的字符串重复数据删除,以避免浪费内存,减少内存占用。------摘自openjdk
觉得写的不错记得点赞收藏哦!