Java 字符串常量池 (String Constant Pool) 的位置在不同的 JDK 版本中有所不同:
JDK 1.6 及之前:
- 位置: 字符串常量池位于方法区的永久代 (PermGen) 中。
- 特点:
- 永久代使用 JVM 内存。
- 永久代大小固定,容易发生
OutOfMemoryError: PermGen space
。 - 字符串常量池中存储的是字符串实例 (对象)。
String.intern()
方法会将字符串添加到字符串常量池中 (如果池中已存在相同内容的字符串,则返回池中的引用;否则,将字符串添加到池中,并返回池中的引用)。
JDK 1.7:
- 位置: 字符串常量池被移到了 Java 堆 (Heap) 中。
- 变化:
- 字符串常量池不再位于方法区/永久代,而是位于堆中。
- 这意味着字符串常量池的大小受堆大小的限制,而不再受永久代大小的限制。
- 原因:
- 永久代大小固定,容易 OOM。 将字符串常量池移到堆中, 可以减少 OOM 的风险。
- 堆是垃圾回收的主要区域,将字符串常量池移到堆中,可以更有效地进行垃圾回收。
JDK 1.8 及之后:
- 位置: 字符串常量池仍然位于 Java 堆 (Heap) 中。
- 变化:
- HotSpot VM 使用元空间 (Metaspace) 替代了永久代来实现方法区,但字符串常量池仍然在堆中。
总结:
JDK 版本 | 字符串常量池位置 |
---|---|
1.6 及之前 | 方法区 (永久代 PermGen) |
1.7 | Java 堆 (Heap) |
1.8 及之后 | Java 堆 (Heap) |
字符串常量池中存储的是什么?
- JDK 1.6 及之前: 字符串常量池中存储的是字符串实例 (对象)。
- JDK 1.7 及之后: 字符串常量池中存储的是字符串对象的引用,而不是实例本身。 字符串实例本身仍然存储在 Java 堆中。
String.intern()
方法:
String.intern()
方法是一个 native 方法,用于将字符串添加到字符串常量池中(或从池中获取引用)。- JDK 1.6 及之前:
- 如果字符串常量池中已存在相同内容的字符串,则返回池中的引用。
- 如果字符串常量池中不存在相同内容的字符串,则将该字符串复制到字符串常量池中,并返回池中的引用。
- JDK 1.7 及之后:
- 如果字符串常量池中已存在相同内容的字符串,则返回池中的引用。
- 如果字符串常量池中不存在相同内容的字符串,则将该字符串对象的引用添加到字符串常量池中,并返回该引用(而不是复制字符串)。
示例 (JDK 1.8):
public class StringPoolExample {
public static void main(String[] args) {
String s1 = new String("hello"); // 创建两个对象:一个 "hello" 字符串对象,一个 s1 引用
String s2 = "hello"; // 直接从字符串常量池中获取引用
String s3 = s1.intern(); // 将 s1 指向的字符串对象的引用添加到字符串常量池 (如果池中已存在,则返回池中的引用)
System.out.println(s1 == s2); // false (s1 指向堆中的对象, s2 指向字符串常量池中的引用)
System.out.println(s2 == s3); // true (s2 和 s3 都指向字符串常量池中的同一个引用)
String s4 = new String("world") + new String("!"); // 创建多个对象
String s5 = "world!";
String s6 = s4.intern(); // 将 "world!" 字符串对象的引用添加到字符串常量池
System.out.println(s4 == s5); // false
System.out.println(s5 == s6); // true
String s7 = new String("java");
String s8 = s7.intern();
String s9 = "java";
System.out.println(s7 == s8); // false, 在 OpenJDK/HotSpot 中, 首次出现的 "java" 字符串, 是由 `sun.misc.Version` 类加载的
System.out.println(s8 == s9); // true
}
}
为什么要将字符串常量池移到堆中?
- 避免 PermGen 溢出: 永久代大小固定,容易发生
OutOfMemoryError: PermGen space
。将字符串常量池移到堆中,可以减少 OOM 的风险。 - 更容易进行垃圾回收: 堆是垃圾回收的主要区域,将字符串常量池移到堆中,可以更有效地进行垃圾回收。
- 简化 JVM 结构: 移除永久代可以简化 JVM 的实现。
总结:
字符串常量池的位置在不同的 JDK 版本中有所不同。从 JDK 1.7 开始,字符串常量池被移到了 Java 堆中。 字符串常量池中存储的是字符串对象的引用(JDK 1.7 及之后),而不是字符串实例本身。 String.intern()
方法可以将字符串添加到字符串常量池中(或从池中获取引用)。