JVM学习-StringTable字符串常量池
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字符串常量池相关推荐
- JVM学习记录-字符串常量池
学习宋红康老师和深入理解java虚拟机中关于String的理解和笔记,如下是自己的学习整理和理解,如果有理解错误望指正 文章目录 1.字符串常量池 字符串常量池的位置 字符串常量池的哈希表结构 案例代 ...
- JVM - 深入剖析字符串常量池
文章目录 字符串常量池 位置的变更 JVM对字符串常量池的优化 字符串的常见创建方式 (1.7+) 直接赋值字符串 new String() intern 经典面试题 下列代码创建几个对象 案例 案例 ...
- JVM——字符串常量池详解
关注微信公众号:CodingTechWork,一起学习进步. 引言 在Java开发中不管是前后端交互的JSON串,还是数据库中的数据存储,我们常常需要使用到String类型的字符串.作为最常用也是 ...
- 详解JVM常量池、Class常量池、运行时常量池、字符串常量池(心血总结)
写在前面:博主是一位普普通通的19届双非软工在读生,平时最大的爱好就是听听歌,逛逛B站.博主很喜欢的一句话花开堪折直须折,莫待无花空折枝:博主的理解是头一次为人,就应该做自己想做的事,做自己不后悔的事 ...
- Java中的字符串常量池详细介绍
Java中字符串对象创建有两种形式,一种为字面量形式,如String str = "droid";,另一种就是使用new这种标准的构造对象的方法,如String str = new ...
- java中的字符串常量池_java字符串常量池
字符串常量池SCP jdk1.6是放在永久代(8中叫方法区或叫元空间)中; jdk1.7+中,字符串常量池放入了堆中,注意运行时常量依然存放在方法区,例如,Integer a = 40:Java在编译 ...
- java定义字符串常量_Java中的字符串常量池
ava中字符串对象创建有两种形式,一种为字面量形式,如String str = "droid";,另一种就是使用new这种标准的构造对象的方法,如String str = new ...
- String类的学习笔记(中):介绍字符串的不可变性和字符串常量池
本文介绍了String类字符串的不可变性和字符串常量池,主要包括 如何保证字符串不可变, 如何对字符串的修改. 为什么字符串要设置不可变, 字符串常量池的创建和了解,简单的字符串常量池图, 以及如何将 ...
- JVM之(执行引擎、字符串常量池、垃圾回收)-总结
JVM之(执行引擎.性能监控.垃圾回收)-总结 如想了解更多更全面的Java必备内容可以阅读:所有JAVA必备知识点面试题文章目录: JAVA必备知识点面试题 注意: 本篇主要以HotSpot虚拟机为 ...
最新文章
- mobx使用数组提示越界_Mobx-State-Tree-分配给数组类型
- jsp java 登陆_jsp+java servlet实现简单用户登录
- 前端 Offer 提速:如何写出有亮点的简历
- 梯度下降法预测波士顿房价以及简单的模型评估
- php输出数据过大,PHPExcel导出数据量过大处理
- css 单行/多行文字垂直居中问题
- java sortedlist 固定容量_Java8 使用 stream().sorted()对List集合进行排序的操作
- 移动端真机调试的两种方法
- marqueeview更改字体颜色_安卓手机上可以编辑字体的便签软件哪个好?
- 基于51单片机GPS的导航系统设计(3)---毕设论文
- creator贴图纹理压缩(creator2.4.x 实现ETC2和ASTC)
- JAVA计算机毕业设计科院垃圾分类系统部署+源码+数据库+系统+lw文档
- Unity中在运行时获取AnimationClip中的关键帧信息
- 财务自由人(Financial free man)
- ipad2越狱完成!
- 引用android-support-v7-appcompat库文件出错的问题
- 让你的Lable拥有qq表情!
- 怎么从FAT32分区中恢复数据?
- latex写姓名_用Latex写学术论文:作者(Author)摘要(Abstract)
- 基于FPGA的CNN卷积神经网络加速器