首页 > 编程知识 正文

string常量池在哪个地方,元空间字符串常量池

时间:2023-05-05 10:33:27 阅读:120005 作者:3235

本文介绍HotSpot的String Pool和字符串常量池。 用比较简单的文章,大家几分钟就读完了。

在Java世界中,构建Java对象是一项相对繁重的任务,也需要垃圾回收,但缓存池是为了缓解这一问题。

让我们看看基本类型的软件包类缓存。 默认情况下,Integer缓存- 128到127个区间的值。 长整型和短整型也缓存此区间的值。 Byte只能表示-127 ~ 128范围的值,全部缓存。 Character缓存的值介于0和127之间。 浮动和双精度没有缓存的意义。

Integer可以通过设置Java.lang.integer.integer cache.high来扩大缓存部分

虽然String不是基本类型,但也有使用String Pool缓存String对象的机制。 如果在APP应用程序中多次使用" Java "字符串,则每次使用该字符串时,您一定不想在堆中重新创建新对象。

当然,Integer、Long和String等类的对象之所以可以缓存,是因为它们是不变类

基本包装器缓存池使用数组进行缓存,但字符串型,在JVM内部使用HashTable进行缓存。 我们知道HashTable的结构是一个数组,数组中的每个元素都是一个链表。 与我们平时使用的HashTable不同,JVM内部的这个HashTable不能动态扩展。

创建和回收

如果在程序中使用双引号表示字符串,则该字符串将包含在String Pool中。 当然,这里指的是已经加载到JVM中的类。

这不是严格的说法,请参考评论区的讨论。

它也是String#intern ()方法。 此方法的作用如下:

如果Pool中不存在字符串,则将记录添加到Pool中,并返回对Pool的引用。

如果已经在Pool中,则直接返回Pool中的引用。

只要String Pool中的String对象无法到达GC Roots,它们就可以回收。

如果Pool中对象过多,则YGC可能会变长。 YGC的情况下,需要扫描String Pool,所以请看笨蛋大人物的文章《JVM源码分析之String.intern()导致的YGC不断变长》。

探讨String Pool的实现

1、首先考虑String Pool的空间问题。

在Java 6中,字符串池位于PermGen Space中。 PermGen的问题是,可以使用-XX:MaxPermSize=N设置永久空间大小,但无论如何设置,最终都是固定的。

因此,Java 6必须尽可能小心地使用String.intern ()方法。 否则,很容易导致内存输出错误。

到了Java 7,zxdhy已经开始移除PermGen Space,首先将String Pool移动到堆中。

将String Pool放入堆中会固定堆的大小,但在这种情况下,只需要调整堆的大小以优化APP应用程序。

到了Java 8,PermGen被完全丢弃,出现堆外内存区域MetaSpace,String Pool相应地从堆迁移到了MetaSpace。

在Java 8中,字符串池仍然位于Heap Space中。 感谢评论区的读者指出错误。 请读一下我之后写的关于MetaSpace的文章。 那篇文章深刻分析了MetaSpace的构成。

2、接下来讨论String Pool的实现问题。

如上所述,String Pool是使用HashTable实现的。 这个HashTable不能扩展。 这意味着单个bucket中的链表很长,性能很可能会下降。

在Java 6中,此HashTable固定的bucket数为1009,添加了允许设置此值的选项(-XX:StringTableSize=N )。 到了Java7(7u40 ),zxdhy已将此默认值提高到60013。 Java 8也仍然使用该值,并且该值对于大多数APP应用程序来说足够用。 当然,如果代码经常使用String#intern (),则必须手动设置此值。

为什么是1009而不是1000或1024呢? 1009是质数,所以有助于达到更好的散列。 和60013一样。

JVM内部的HashTable没有扩展,但并不意味着不rehash。 注意到散列不均匀性时刷新。 这里不介绍。

3、观察String Pool使用情况。

由JVM提供-

XX:+PrintStringTableStatistics 启动参数来帮助我们获取统计数据。

遗憾的是,只有在 JVM 退出的时候,JVM 才会将统计数据打印出来,JVM 没有提供接口给我们实时获取统计数据。

SymbolTable statistics:

Number of buckets : 20011 = 160088 bytes, avg 8.000

Number of entries : 10923 = 262152 bytes, avg 24.000

Number of literals : 10923 = 425192 bytes, avg 38.926

Total footprint : = 847432 bytes

Average bucket size : 0.546

Variance of bucket size : 0.545

Std. dev. of bucket size: 0.738

Maximum bucket size : 6

## 看下面这部分:

StringTable statistics:

Number of buckets : 60003 = 480024 bytes, avg 8.000

Number of entries : 4000774 = 96018576 bytes, avg 24.000

Number of literals : 4000774 = 1055252184 bytes, avg 263.762

Total footprint : = 1151750784 bytes

Average bucket size : 66.676

Variance of bucket size : 19.843

Std. dev. of bucket size: 4.455

Maximum bucket size : 84

统计数据中包含了 buckets 的数量,总的 String 对象的数量,占用的总空间,单个 bucket 的链表平均长度和最大长度等。

上面的数据是在 Java 8 的环境中打印出来的,Java 7 的信息稍微少一些,主要是没有 footprint 的数据:

StringTable statistics:

Number of buckets : 60003

Average bucket size : 67

Variance of bucket size : 20

Std. dev. of bucket size: 4

Maximum bucket size : 84

测试 String Pool 的性能

接下来,我们来跑个测试,测试下 String Pool 的性能问题,并讨论 -XX:StringTableSize=N 参数的作用。

我们将使用 String#intern() 往字符串常量池中添加 400万 个不同的长字符串。

package com.javadoop;

import java.lang.ref.WeakReference;

import java.util.ArrayList;

import java.util.List;

import java.util.WeakHashMap;

public class StringTest {

public static void main(String[] args) {

test(4000000);

}

private static void test(int cnt) {

final List lst = new ArrayList(1024);

long start = System.currentTimeMillis();

for (int i = 0; i < cnt; ++i) {

final String str = "Very very very very very very very very very very very very very very " +

"very long string: " + i;

lst.add(str.intern());

if (i % 200000 == 0) {

System.out.println(i + 200000 + "; time = " + (System.currentTimeMillis() - start) / 1000.0 + " sec");

start = System.currentTimeMillis();

}

}

System.out.println("Total length = " + lst.size());

}

}

我们每插入 20万 条数据,输出一次耗时。

# 编译

javac -d . StringTest.java

# 使用默认 table size (60013) 运行一次

java -Xms2g -Xmx2g com.javadoop.StringTest

# 设置 table size 为 400031,再运行一次

java -Xms2g -Xmx2g -XX:StringTableSize=400031 com.javadoop.StringTest

从左右两部分数据可以很直观看出来,插入的性能主要取决于链表的平均长度。当链表平均长度为 10 的时候,我们看到性能是几乎没有任何损失的。

还是那句话,根据自己的实际情况,考虑是否要设置 -XX:StringTableSize=N,还是使用默认值。

讨论自建 String Pool

这一节我们来看下自己使用 HashMap 来实现 String Pool。

这里我们需要使用 WeakReference:

private static final WeakHashMap> pool

= new WeakHashMap>(1024);

private static String manualIntern(final String str) {

final WeakReference cached = pool.get(str);

if (cached != null) {

final String value = cached.get();

if (value != null) {

return value;

}

}

pool.put(str, new WeakReference(str));

return str;

}

我们使用 1000 * 1000 * 1000 作为入参 cnt 的值进行测试,分别测试 [1] 和 [2]:

private static void test(int cnt) {

final List lst = new ArrayList(1024);

long start = System.currentTimeMillis();

for (int i = 0; i < cnt; ++i) {

// [1]

lst.add(String.valueOf(i).intern());

// [2]

// lst.add(manualIntern(String.valueOf(i)));

if (i % 200000 == 0) {

System.out.println(i + 200000 + "; time = " + (System.currentTimeMillis() - start) / 1000.0 + " sec");

start = System.currentTimeMillis();

}

}

System.out.println("Total length = " + lst.size());

}

测试结果,2G 的堆大小,如果使用 String#intern(),大概在插入 3000万 数据的时候,开始进入大量的 FullGC。

而使用自己写的 manualIntern(),大概到 1400万 的时候,就已经不行了。

没什么结论,如果要说点什么的话,那就是不要自建 String Pool,没必要。

小结

记住有两个 JVM 参数可以设置:-XX:StringTableSize=N、-XX:+PrintStringTableStatistics

StringTableSize,在 Java 6 中,是 1009;在 Java 7 和 Java 8 中,默认都是 60013,如果有必要请自行扩大这个值。

参考资料

(全文完)

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。