JVM-(十)StringTable字符串常量池
本文最后更新于:June 16, 2022 pm
积土成山,风雨兴焉;积水成渊,蛟龙生焉;积善成德,而神明自得,圣心备焉。故不积跬步,无以至千里,不积小流无以成江海。齐骥一跃,不能十步,驽马十驾,功不在舍。面对悬崖峭壁,一百年也看不出一条裂缝来,但用斧凿,能进一寸进一寸,能进一尺进一尺,不断积累,飞跃必来,突破随之。
目录
String的基本特性
- String:字符串,使用一对双引号引起来表示。
- String声明为final的,不可被继承。
- String实现了Serializable接口:表示字符串是支持序列化的;实现了Comparable接口:表示String可以比较大小。
- String在JDK8及以前内部定义了fianl char[] value用于存储字符串数据。而在JDK9时改为了byte[]数组加上一个编码标记。
String代表不可变的字符序列,简称:不可变性。
- 当对字符串重新赋值时,需要重写指定内存区域赋值,不能使用原有的value进行赋值。
- 当对现有的字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。
- 当调用String的replac()方法修改指定字符或字符串时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。
通过字面量的方式(区别于new)给一个字符串赋值,此时的字符串值声明在字符串常量池中。
- 字符串常量池中是不会存储相同内容的字符串。
- String的String Pool是一个固定大小的Hashtable,默认值大小长度是1009(JDK6,注意不同JDK版本值不一样)。如果放进String Pool的String非常多,就会造成Hash冲突严重,从而导致链表会很长,而链表长了后直接会造成的影响就是当调用String.intern时性能会大幅下降。
- 使用
-XX:StringTableSize
可以设置StringTable的长度。 - 在JDK6中StringTable是固定的,就是1009的长度,所以如果常量池中的字符串过多就会导致效率下降很快。StringTableSize设置没有要求。在JDK7中,StringTable的长度默认值是60013,从JDK8开始1009是可设置的最小值。
运行代码:(主要目的就是让程序一直处于运行状态即可)
1 |
|
打开命令窗口,输入命令:
1 |
|
可以看见默认值为60013(JDK8)。
然后再通过设置虚拟机参数指定StringTable长度:-XX:StringTableSize=20。然后再跑上面两个命令,发现会报错如下:
1 |
|
大概意思就说说:我们设置的值不对,必须是在1009到2305843009213693951之间的数。这也是上面提到过的1009是设置的最小值。
然后将20改为2000再次尝试,发现可以正常查看设置之后的值为2000。
📢注意:当前测试环境为JDK8,如果环境为JDK6,那么可以设置任何值!!!还有就是JDK7中,虽然默认值为60013,但是参数设置时依旧可以设为任意值!!
- JDK6:默认值为1009,设置参数-XX:StringTableSize的值可以为任意值。
- JDK7:默认值为60013,设置参数-XX:StringTableSize的值可以为任意值。
- JDK8:默认值为60013,设置参数-XX:StringTableSize的值的最小值为1009。
String的内存分配
关于String字符串的创建和内存分配,具体也可见《JAVA知识点-深入理解String字符串的创建和比较 》
- 在Java语言中有8种基本数据类型和一种比较特殊的类型String。这些类型为了使它们在运行过程中速度更快、更节省内存,都提供了一种常量池的概念。
- 常量池就类似一个Java系统级别提供的缓存。8中基本数据类型的常量池都是系统协调的,String类型的常量池比较特殊,主要有两种使用方式:
- 直接使用双引号声明出来的String对象会直接存储在常量池中。如:String str = “tothefor.com”;
- 如果不是用双引号声明的String对象,可以使用String提供的intern()方法将其放到常量池中,并返回此串的地址。如果在放入之前常量池中没有此字符串,则将此字符串放入并返回此地址;如果在放入之前常量池中就已经存在了此字符串,那么将返回已经存在的字符串的地址。
字符串常量池在JVM中的位置
- Java 6及以前,字符串常量池存放在永久代。
- Java 7中,字符串常量池的位置调整到了Java堆内。
- 所有的字符串都保存在堆(heap)中,和其他普通对象一样,这样可以让你在进行调优应用时仅需要调整堆大小就可以了。
- 字符串常量池概念原本使用得比较多,但是这个改动使得我们有足够的理由让我们重新考虑在Java7中使用String.intern()。
- Java 8元空间,字符串常量在堆中。
intern()的使用
📢注意:在JDK6和JDK7及其之后的区别。
具体也可见《JAVA知识点-深入理解String字符串的创建和比较 》 ,这里就只是补充一点。
示例1:
1 |
|
这个比较简单,因为第一行的s是在堆空间中,而s2是在字符串常量池中。因为在第一行的时候,字符串常量池中就已经有了字符串”1”,所以s.intern();返回的地址就是在字符串常量池中的”1”。所以,这两个就不相等。
示例2:
1 |
|
JDK6:在执行String.intern()方法时,如果字符串常量池中没有当前字符串,则会创建该字符串的一个对象。
而第一行执行完后,常量池中有:”1”;堆中有:”1”、”1”、”11”。而s3就是堆中的”11”;当执行s3.intern();时,发现常量池中并没有字符串”11”,所以就会创建一个该字符串的对象
。执行第三行的时候,发现常量池中已经存在了字符串”11”,所以直接返回已经存在的字符串地址。此时,s3指向的是堆中的”11”,而s4指向的是常量池中的”11”,所以两个不相等。
JDK7:在执行String.intern()方法时,如果字符串常量池中没有当前字符串,则会在常量池中创建一个指向该字符串的引用。即常量池中存储的地址就是堆中该字符串的地址,两个共用一个地址。原因:因为在JDK7及其之后,字符串常量池已经放在了堆中了,所以为了节省空间,常量池中直接引用堆(非字符串常量池)中的地址即可。
而第一行执行完后,常量池中有:”1”;堆中有:”1”、”1”、”11”。而s3就是堆中的”11”;当执行s3.intern();时,发现常量池中并没有字符串”11”,所以就会创建一个指向该字符串的引用
。执行第三行的时候,发现常量池中已经存在了字符串”11”,所以直接返回已经存在的字符串地址。此时,s3指向的是堆中的”11”,而s4指向的是常量池中的”11”,但常量池中的”11”的地址其实就是堆中的”11”的地址,所以两个相等。
示例3:
1 |
|
JDK8:而第一行执行完后,常量池中有:”1”;堆中有:”1”、”1”、”11”。而s3就是堆中的”11”;
然后执行第二行代码,因为此时在字符串常量池中还没有字符串”11”,所以此时会在字符串常量池进行创建一个字符串”11”(这里需要注意和之前的区别,这里不再是引用其他的,而是实实在在的一个对象)。
当执行s3.intern();时,发现常量池中有字符串”11”,则返回已经有的字符串的地址。这里并没有什么用。
所以,最后s3和s4是不相等的。因为s3是在堆中,而s4是在字符串常量池中。
补充:
1 |
|
垃圾回收
设置虚拟机参数:
1 |
|
运行代码:
1 |
|
输出结果:
1 |
|
其中,第一行表示进行了垃圾收集,是在年轻代中进行的收集。4096K->496K表示从原来的4096K到现在的496K。
G1的String去重操作(了解)
背景:对许多Java应用(有大有小)做的测试得出的结果:
- 堆存活数据集合里面String对象占了25%。
- 堆存活数据集合里面重复的String对象有13.5%。
- String对象的平均长度是45。
许多大规模的Java应用的瓶颈在于内存,测试表明,在这些类型的应用里面,Java堆中存活的数据集合差不多25%是String对象。更进一步,这里面差不多一半String对象是重复的,重复的意思是说:string1.equals(string2)=true。堆上存在重复的String对象必然是一种内存的浪费。这个项目将在G1垃圾收集器中实现自动持续对重复的String对象进行去重,这样就能避免浪费内存。
实现
- 当垃圾收集器工作的时候,会访问堆上存活的对象。对每一个访问的对象都会检查是否是候选的要去重的String对象。
- 如果是,把这个对象的一个引用插入到队列中等待后续的处理。一个去重的线程在后台运行,处理这个队列。处理队列的一个元素意味着从队列删除这个元素,然后尝试去重它引用的String对象。
- 使用一个hashtable来记录所有的被String对象使用的不重复的char数组。当去重的时候,会查这个hashtable,来看堆上是否已经存在一个一模一样的char数组。
- 如果存在,String对象会被调整引用那个数组,释放对原来的数组的引用,最终会被垃圾收集器回收掉。
- 如果查找失败,char数组会被插入到hashtable,这样以后的时候就可以共享这个数组了。
命令行选项
- UseStringDeduplication(bool):开启String去重,默认是不开启的,需要手动开启。
- PrintStringDeduplicationStatistics(bool):打印详细的去重统计信息。
- StringDeduplicationAgeThreshold(uintx):达到这个年龄的String对象被认为是去重的候选对象。
本文作者: 墨水记忆
本文链接: https://tothefor.com/DragonOne/b0773da2.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!