StringTable

  • 1.StringTable的特性
    • 1.1.面试题
    • 1.2.常量池与串池的关系
    • 1.3.字符串的拼接
    • 1.4.编译器优化
    • 1.5.intern方法
      • 1.5.1.intern方法(1.8)
      • 1.5.2.intern方法(1.6)
    • 1.6.StringTable的特性总结
  • 2.StringTable位置
  • 3.StringTable垃圾回收
  • 4.StringTable性能调优

1.StringTable的特性

1.1.面试题

先看几道面试题:

String s1 = "a";
String s2 = "b";
String s3 = "a" + "b";
String s4 = s1 + s2;
String s5 = "ab";
String s6 = s4.intern();
// 问
System.out.println(s3 == s4);
System.out.println(s3 == s5);
System.out.println(s3 == s6);
String x2 = new String("c") + new String("d");
String x1 = "cd"; x2.intern();
// 问,如果调换了【最后两行代码】的位置呢,如果是jdk1.6呢
System.out.println(x1 == x2);

以上代码的运行结果如何,如果尚有疑惑,请看下面的分析

1.2.常量池与串池的关系

public static void main(String[] args) {String s1 = "a"; // 懒惰的String s2 = "b";String s3 = "ab";
}

反编译Demo1.class(之前需要运行将.java文件生成.class文件)

F:\IDEA\projects\jvm>javap -v F:\IDEA\projects\jvm\out\production\untitled\Demo1.class

常量池中信息

Constant pool:#1 = Methodref          #6.#24         // java/lang/Object."<init>":()V#2 = String             #25            // a#3 = String             #26            // b#4 = String             #27            // ab#5 = Class              #28            // Demo1#6 = Class              #29            // java/lang/Object......

main方法字节码信息

public static void main(java.lang.String[]);descriptor: ([Ljava/lang/String;)Vflags: ACC_PUBLIC, ACC_STATICCode:stack=1, locals=4, args_size=10: ldc           #2                  // String a2: astore_13: ldc           #3                  // String b5: astore_26: ldc           #4                  // String ab8: astore_39: returnLineNumberTable:line 9: 0line 10: 3line 11: 6line 14: 9LocalVariableTable:Start  Length  Slot  Name   Signature0      10     0  args   [Ljava/lang/String;3       7     1    s1   Ljava/lang/String;6       4     2    s2   Ljava/lang/String;9       1     3    s3   Ljava/lang/String;

可以看到ldc #2,也就是到常量池中2号位置加载信息,这个例子中2号位置对应字符串对象String a(注释中),然后astore_1也就是把加载好的a字符串对象存入1号的局部变量。低下的 LocalVariableTable:也就是main方法栈帧运行时局部变量表中的变量,编号是1.同理,ldc #3,String b存入到局部变量表中2号位置去,ab字符串存入到3中去。

看完上面代码之后,我们要理清楚常量池与串池的关系。
编译后常量池中的信息,都会被加载到运行时常量池中, 这时 a b ab 都是常量池中的符号,还没有变为 java 字符串对象
当具体执行到引用它的代码上,就会变成java字符串对象
ldc #2 会把 a 符号变为 “a” 字符串对象
在变为a字符串对象之后,还要准备好一块空间,即StringTable字符串常量池,刚开始里面是空的,将a变为字符串对象之后,就会去StringTable中找,看有没有相同的key,在数据结构上是一个hash表,长度固定,不能扩容。如果没有,就会把a放入串池
只有执行到用到它的代码,才开始创建字符串对象,放入到常量池中
它们在行为上是懒惰的

如果串池中有了,就会使用串池中的对象
ldc #3 会把 b 符号变为 “b” 字符串对象
ldc #4 会把 ab 符号变为 “ab” 字符串对象
最终
StringTable [ “a”, “b” ,“ab” ] hashtable 结构,不能扩容

1.3.字符串的拼接

public static void main(String[] args) {String s1 = "a"; // 懒惰的String s2 = "b";String s3 = "ab";String s4 = s1 + s2; // new StringBuilder().append("a").append("b").toString()  new String("ab")}

将上述代码反编译,生成反编译结果,对于String s4 = s1 + s2;部分的代码如下:

         9: new           #5                  // class java/lang/StringBuilder12: dup13: invokespecial #6                  // Method java/lang/StringBuilder."<init>":()V16: aload_117: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;20: aload_221: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;24: invokevirtual #8                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;27: astore        4

它先创建了StringBuilder()对象,然后调用了init()构造方法。aload_1把s1这个参数加载进来,即从局部变量表中拿到s1参数a,与astore1相反。接下来调用了append方法,s1作为参数。aload2又把s2拿到了,即字符串b,作为append方法。最后使用了一个toString方法,最后astore_4,即把toString转换后的结果存入到4号的局部变量中。
我们可以看看StringBuilder的toString原码

public String toString() {// Create a copy, don't share the arrayreturn new String(value, 0, count);
}

即创建了一个新的值为ab的对象,存入到s4这个对象中。

String s3 = "ab";
String s4 = s1 + s2; // new StringBuilder().append("a").append("b").toString()  new String("ab")
System.out.println(s3==s4);

结果是false

虽然它们值是相同的,但是s3是在串池中,而s4是new出来的字符串对象,是在堆里面的。它们是两个对象。所以打印false

1.4.编译器优化

public static void main(String[] args) {String s1 = "a"; // 懒惰的String s2 = "b";String s3 = "ab";String s4 = s1 + s2; // new StringBuilder().append("a").append("b").toString()  new String("ab")String s5 = "a" + "b";  // javac 在编译期间的优化,结果已经在编译期确定为ab}

反编译
生成的最后一行代码的字节码如下:

29: ldc           #4                  // String ab
31: astore        5

我们可以看到,它不是先找a再找b最后拼接到一起,而是直接找到的就是直接拼接好的ab这个符号,并且存入到局部变量里面,下标为5的位置。

而这是String s3 = “ab”;的字节码

6: ldc           #4                  // String ab
8: astore_3

在开始的时候,串池没有ab这个对象,s3创建出来了,就放入到串池,而s5再次引用ab这个字符串时候,就先会去字符串常量池中找,找到了,然后就会直接用串池中字符串对象。所以存储s3的对象和存储s5的对象,都是串池中的字符串对象。
最后发现下面结果输出为真。

String s3 = "ab";
String s5 = "a" + "b";  // javac 在编译期间的优化,结果已经在编译期确定为ab
System.out.println(s3 == s5);

结果为true
javac 在编译期间的优化,结果已经在编译期确定为ab
而上面拼接的是变量,既然是变量,说明以后引用的时候可能会发生修改,所以值不能确定。所以在运行期间用stringbuilder的方式来拼接。

1.5.intern方法

1.5.1.intern方法(1.8)

调用字符串对象的intern方法,会将该字符串对象尝试放入到串池中

  • 如果串池中没有该字符串对象,则放入成功
  • 如果有该字符串对象,则放入失败

无论放入是否成功,都会返回串池中的字符串对象

注意:此时如果调用intern方法成功,堆内存与串池中的字符串对象是同一个对象;如果失败,则不是同一个对象

我们有以下代码

String s=new String("a")+new String("b");

经过前面学习,我们知道了
常量a,b都被放入到了常量池中
new出来的两个对象String(“a”)和String(“b”)被放入到了堆中。他们值虽然相同,但是对象不同。
s又引用了对象,new String(“ab”),它只存在于堆中,不存在串池中。
即常量池中存在[“a”,“b”]
而堆中存在new String(“a”),new String(“b”),new String(“ab”)
我们能不能把ab存入到常量池中呢,可以通过intern方法
intern方法将这个字符串对象尝试放入串池,如果有则不会放入,如果没有则放入串池,会把串池中的对象返回。

public class Main {public static void main(String[] args) {//"a" "b" 被放入串池中,str则存在于堆内存之中String str = new String("a") + new String("b");//调用str的intern方法,这时串池中没有"ab",则会将该字符串对象放入到串池中,此时堆内存与串池中的"ab"是同一个对象String st2 = str.intern();//给str3赋值,因为此时串池中已有"ab",则直接将串池中的内容返回String str3 = "ab";//因为堆内存与串池中的"ab"是同一个对象,所以以下两条语句打印的都为trueSystem.out.println(str == st2);System.out.println(str == str3);}
}

返回结果为true,true
如果将在最开始定义了String x="ab"呢

public class Main {public static void main(String[] args) {//此处创建字符串对象"ab",因为串池中还没有"ab",所以将其放入串池中String str3 = "ab";//"a" "b" 被放入串池中,str则存在于堆内存之中String str = new String("a") + new String("b");//此时因为在创建str3时,"ab"已存在与串池中,所以放入失败,但是会返回串池中的"ab"String str2 = str.intern();//falseSystem.out.println(str == str2);//falseSystem.out.println(str == str3);//trueSystem.out.println(str2 == str3);}
}

1.5.2.intern方法(1.6)

调用字符串对象的intern方法,会将该字符串对象尝试放入到串池中

如果串池中没有该字符串对象,会将该字符串对象复制一份,再放入到串池中
如果有该字符串对象,则放入失败
无论放入是否成功,都会返回串池中的字符串对象

注意:此时无论调用intern方法成功与否,串池中的字符串对象和堆内存中的字符串对象都不是同一个对象

1.6.StringTable的特性总结

常量池中的字符串仅是符号,只有在被用到时才会转化为对象

利用串池的机制,来避免重复创建字符串对象

字符串变量拼接的原理是StringBuilder

字符串常量拼接的原理是编译器优化

可以使用intern方法,主动将串池中还没有的字符串对象放入串池中

注意:无论是串池还是堆里面的字符串,都是对象

用来放字符串对象且里面的元素不重复

面试结果如下:

public static void main(String[] args) {String s1 = "a";String s2 = "b";String s3 = "a" + "b"; // abString s4 = s1 + s2;   // new String("ab")String s5 = "ab";String s6 = s4.intern();// 问System.out.println(s3 == s4); // falseSystem.out.println(s3 == s5); // trueSystem.out.println(s3 == s6); // trueString x2 = new String("c") + new String("d"); // new String("cd")x2.intern();String x1 = "cd";// 问,如果调换了【最后两行代码】的位置呢System.out.println(x1 == x2);  //调换前true,调换后fals}

此时所有的问题是不是都迎刃而解了

2.StringTable位置

在1.6中是常量池的一部分,随常量池存储在永久代中,1.7开始之后就放入到了堆中。因为永久代的内存回收效率很低,永久代很难触发垃圾回收,需要等到老年代空间不足才会回收,触发效率不高。Springtable是非常常用的,放入到常量池中容易导致永久代内存不足。而堆里面Springtable触发垃圾回收的情况比较简单。

3.StringTable垃圾回收

StringTable在内存紧张时,会发生垃圾回收。

4.StringTable性能调优

SpringTable底层是哈希表,哈希表性能与其大小有关,如果哈希表桶的个数比较多,元素分散,哈希碰撞的几率就比较小,查找效率较高。
因为StringTable是由HashTable实现的,所以可以适当增加HashTable桶的个数,来减少字符串放入串池所需要的时间

-XX:StringTableSize=xxxx

考虑是否需要将字符串对象入池
可以通过intern方法减少重复入池,相同的地址intern之后,在内存中只会存储一份,这样就能减少字符串对内存占用。

JVM学习-StringTable字符串常量池相关推荐

  1. JVM学习记录-字符串常量池

    学习宋红康老师和深入理解java虚拟机中关于String的理解和笔记,如下是自己的学习整理和理解,如果有理解错误望指正 文章目录 1.字符串常量池 字符串常量池的位置 字符串常量池的哈希表结构 案例代 ...

  2. JVM - 深入剖析字符串常量池

    文章目录 字符串常量池 位置的变更 JVM对字符串常量池的优化 字符串的常见创建方式 (1.7+) 直接赋值字符串 new String() intern 经典面试题 下列代码创建几个对象 案例 案例 ...

  3. JVM——字符串常量池详解

    关注微信公众号:CodingTechWork,一起学习进步. 引言   在Java开发中不管是前后端交互的JSON串,还是数据库中的数据存储,我们常常需要使用到String类型的字符串.作为最常用也是 ...

  4. 详解JVM常量池、Class常量池、运行时常量池、字符串常量池(心血总结)

    写在前面:博主是一位普普通通的19届双非软工在读生,平时最大的爱好就是听听歌,逛逛B站.博主很喜欢的一句话花开堪折直须折,莫待无花空折枝:博主的理解是头一次为人,就应该做自己想做的事,做自己不后悔的事 ...

  5. Java中的字符串常量池详细介绍

    Java中字符串对象创建有两种形式,一种为字面量形式,如String str = "droid";,另一种就是使用new这种标准的构造对象的方法,如String str = new ...

  6. java中的字符串常量池_java字符串常量池

    字符串常量池SCP jdk1.6是放在永久代(8中叫方法区或叫元空间)中; jdk1.7+中,字符串常量池放入了堆中,注意运行时常量依然存放在方法区,例如,Integer a = 40:Java在编译 ...

  7. java定义字符串常量_Java中的字符串常量池

    ava中字符串对象创建有两种形式,一种为字面量形式,如String str = "droid";,另一种就是使用new这种标准的构造对象的方法,如String str = new ...

  8. String类的学习笔记(中):介绍字符串的不可变性和字符串常量池

    本文介绍了String类字符串的不可变性和字符串常量池,主要包括 如何保证字符串不可变, 如何对字符串的修改. 为什么字符串要设置不可变, 字符串常量池的创建和了解,简单的字符串常量池图, 以及如何将 ...

  9. JVM之(执行引擎、字符串常量池、垃圾回收)-总结

    JVM之(执行引擎.性能监控.垃圾回收)-总结 如想了解更多更全面的Java必备内容可以阅读:所有JAVA必备知识点面试题文章目录: JAVA必备知识点面试题 注意: 本篇主要以HotSpot虚拟机为 ...

最新文章

  1. mobx使用数组提示越界_Mobx-State-Tree-分配给数组类型
  2. jsp java 登陆_jsp+java servlet实现简单用户登录
  3. 前端 Offer 提速:如何写出有亮点的简历
  4. 梯度下降法预测波士顿房价以及简单的模型评估
  5. php输出数据过大,PHPExcel导出数据量过大处理
  6. css 单行/多行文字垂直居中问题
  7. java sortedlist 固定容量_Java8 使用 stream().sorted()对List集合进行排序的操作
  8. 移动端真机调试的两种方法
  9. marqueeview更改字体颜色_安卓手机上可以编辑字体的便签软件哪个好?
  10. 基于51单片机GPS的导航系统设计(3)---毕设论文
  11. creator贴图纹理压缩(creator2.4.x 实现ETC2和ASTC)
  12. JAVA计算机毕业设计科院垃圾分类系统部署+源码+数据库+系统+lw文档
  13. Unity中在运行时获取AnimationClip中的关键帧信息
  14. 财务自由人(Financial free man)
  15. ipad2越狱完成!
  16. 引用android-support-v7-appcompat库文件出错的问题
  17. 让你的Lable拥有qq表情!
  18. 怎么从FAT32分区中恢复数据?
  19. latex写姓名_用Latex写学术论文:作者(Author)摘要(Abstract)
  20. 基于FPGA的CNN卷积神经网络加速器

热门文章

  1. 5月16日亮相!华硕ZenFone 6新旗舰曝光:无刘海全面屏加持
  2. 鸿海集团否认郭台铭辞任董事长:只是希望退居二线
  3. 腾讯发布企鹅号“达人计划”清退公告 清退后当月结算将被取消
  4. 拳王虚拟项目公社:利用减肥健身类虚拟资源项目,如何打造一套赚钱系统?
  5. 易到追债贾跃亭 乐视回应:对方无耻甩锅
  6. FTP server的使用【原创】
  7. 内核模块编程之_初窥门径【ZT】
  8. 怎么讲d 盘里的软件弄到桌面_GNOME 2 粉丝喜欢 Mate Linux 桌面的什么?
  9. Linux C代码实现主函数参数选项解析
  10. 我参与的一个项目总结