问题
String.intern()的工作原理?我们应该如何使用它?
基础知识
字符串池(String Pool)
String类在我们日常编程工作中是使用频率非常高的一种对象类型。JVM为了提升性能和减少内存开销,避免字符串的重复创建,其维护了一块特殊的内存空间,即字符串池(String Pool)。字符串池由String类私有的维护。
字符串驻留(String Interning)
VM(Java虚拟机)在运行时将字符串常量或字符串对象存储在一个特殊的池(String Pool)中,以便重用相同的字符串对象,而不是为每个相同的字符串分配新的内存空间。这样可以减少内存的使用,提高效率,特别是在处理大量字符串时。
自动驻留(String Literal Pooling)
当你使用双引号直接定义字符串常量时,JVM会自动将这个字符串加入到字符串池中。如果池中已经存在相同的字符串,JVM会返回池中已存在的那个引用,而不是创建一个新的字符串对象。
手动驻留(String.intern() 方法)
如果你有一个字符串对象,但希望它被加入到字符串池中(即使它不是通过字面量直接创建的),你可以使用String类的intern()方法。
intern()
public String intern()
当调用 intern 方法时,如果池中已经包含一个由 equals(Object) 方法确定的与此 String 对象相等的字符串,则返回池中的字符串。否则,将此 String 对象添加到池中并返回对此 String 对象的引用。— JDK Javadoc
这样设计本质就是为了优化内存的使用,然而这样做却也有缺点:在 OpenJDK 中, String.intern()是本地方法的,它实际上调用 JVM,将 String 驻留在本地 JVM String 池中。这是因为当本地 VM 和 JDK 代码都必须就特定 String 对象的身份达成一致时,String 驻留是 JDK-VM 接口的一部分。那这么做有什么意义呢?其意义如下:
- 每次intern()时都需要跨 JDK-JVM 接口,这会浪费周期。
- 性能取决于本地的HashTable 实现,这可能会落后于高性能 Java 领域中的实现,尤其是在并发访问的情况下。
- 由于 Java 字符串是来自本地 VM 结构的引用,因此它们成为 GC 根集的一部分。在许多情况下,这需要在 GC 暂停期间进行额外的工作来处理。
实验
大量字符串场景下的吞吐量。
源码-字符串驻留用例
@State(Scope.Benchmark)
public class StringIntern {
@Param({
"1", "100", "10000", "1000000"})
private int size;
private StringInterner str;
private CHMInterner chm;
private HMInterner hm;
@Setup
public void setup() {
str = new StringInterner();
chm = new CHMInterner();
hm = new HMInterner();
}
public static class StringInterner {
public String intern(String s) {
return s.intern();
}
}
@Benchmark
public void intern(Blackhole bh) {
for (int c = 0; c < size; c++) {
bh.consume(str.intern("String" + c));
}
}
public static class CHMInterner {
private final Map<String, String> map;
public CHMInterner() {
map = new ConcurrentHashMap<>();
}
public String intern(String s) {
String exist = map.putIfAbsent(s, s);
return (exist == null) ? s : exist;
}<


1895

被折叠的 条评论
为什么被折叠?



