返回
Featured image of post Java - JVM 字符串

Java - JVM 字符串

JDK7以后的字符串常量池.

大部分内容来源这篇美团的文章:https://tech.meituan.com/2014/03/06/in-depth-understanding-string-intern.html,原文非常有深度,这里只捡了一些比较浅的东西出来

JDK6到7的字符串常量池出现了很大变化,为了防止绕晕,这里只拿了7以后的情况出来

  • 直接用双引号声明的 String 对象直接存到常量池

  • intern 方法会从字符串常量池查询当前字符串是否存在,若不存在则将当前字符串放入常量池

  • new 一个字符串出来最多会生成两个对象

    1. 字符串常量池中的引用对象
    2. Java Heap 中的String对象

    当然,如果常量池中原本已经存在了被引用对象,则只会多生成一个对象

字符串常量池

字符串常量池的主体是一个 StringTable

HotSpot VM中使用全局表StringTable记录interned stringStringTableintern 方法类似 HashMap ,本质是HashSet<String>,但不能自动扩容,(JDK6)默认大小 1009,(JDK7)通过-XX:StringTableSize=99991参数指定,默认大小 60013。StringTable只存储对String实例的引用

  • 加载字节码文件时,常量(""圈起来的就是字符串常量)会被加载到运行时常量池 (Runtime Constant Pool) 中,字符串此时只有 CONSTANT_Utf8 ,采用 Lazy-Init ,因此未成为 Java 对象,运行到对应位置再进行对象加载。

类加载阶段

HotSpot VM 在类加载阶段,JVM会创建常量池中的字符串对象实例,但这一过程是惰性的,加载时仅将字面量放入类的运行时常量池,即 CONSTANT_Utf8,而 CONSTANT_String 还没有,也不会进入字符串常量池。(当然静态变量会直接被指定初始值,也就会创建字符串对象,同时保存引用到字符串常量池)

intern()

JDK7中,intern() 方法将在字符串池中保存堆中的引用并返回(如果已经有则直接返回该引用)。对执行方法的对象本身不会造成任何影响,该指哪指哪。以下为例:

String s3 = new String("1") + new String("1");
s3.intern();
String s4 = "11";
System.out.println(s3 == s4);//true

s3 实际上在 Heap 上 new 出了一个"11",且不会保存到常量池中。s3.intern()将这一对象直接保存到了字符串常量池中,s3仍指向这一对象,只是对象本身被保存到了常量池,因此s4显式创建"11"会直接拿常量池中的对象(引用)来用,也就是s3

String s = new String("1");
s.intern();
String s2 = "1";
System.out.println(s == s2);//false

这一段则有很大不同,new String("1") 进行了两步操作——在字符串常量池建了"1",并且还在 Heap 上建了一个额外的 String 对象并赋给 s ,所以s.intern()不会造成任何改变——常量池已经有"1",而s也还是指向 Heap 上的 String 。因此s2 直接指向常量池中的"1"s间接指向1,两者自然不是一个引用。


综合案例

class NewTest1{
    public static String s1="static";  // 第一句
    public static void main(String[] args) {
        String s1=new String("he")+new String("llo"); //第二句
        s1.intern();   // 第三句
        String s2="hello";  //第四句
        System.out.println(s1==s2);//第五句,输出是true。
    }
}

如图,类初始阶段会初始化 s1 ,所以在 main() 运行之前字符串常量池里已经有 "static" 。局部变量 s1 由两个字符串拼接而成,拼接过程实际是 StringBuilderappend,最后会通过 toString() 方法new一个 String 对象,且不会放入字符串常量池。在intern()时才会被放入字符串常量池。s2显式创建,发现字符串常量池中已有,因此直接使用,两者就是同一对象,s1==s2


Oracle JDK 8u20 后的新特性

Oracle JDK 8u20 后出现了 StringDuplication 特性,默认关闭。允许 G1 GC 下的字符串排重,在 JVM 底层允许相同数据的字符串指向同一数据。这一功能下,G1 会在垃圾收集过程中会把新创建的字符串对象放入队列中,在 Young GC 后,并发地将内部数据(JDK 9 以后就是 byte[])一致地字符串进行排重。虽然可以节省内存空间,但也会占用额外 CPU,拖慢 Young GC 的作用。

Licensed under CC BY-NC-SA 4.0
comments powered by Disqus