深入理解JVM(补充章2) 字符串常量池(StringTable)

字符串常量池(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

觉得写的不错记得点赞收藏哦!
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值