Skip to content

JVM 中的常量池到底在哪儿

约 814 字大约 3 分钟

JVM理论常量池

2025-09-24

JDK 8 中:

  • 运行时常量池(类的常量信息)在 元空间(Metaspace)(属于方法区);
  • 字符串常量池堆(Heap)

所以不能简单说 “常量池在堆” 或 “在方法区” ,要区分类型。

结论

常量池类型存储位置说明
运行时常量池元空间(Metaspace)属于 方法区 的一部分
字符串常量池堆(Heap)存放字符串字面量,位于

详细解释

1. 运行时常量池

  • 是类加载后,类文件常量池 被加载到 JVM 内存中的运行时版本。
  • 存储:类级别的常量信息,如:
    • 字面量(int a = 100; 中的 100
    • 符号引用(类名、方法名、字段名)
    • 方法句柄、动态调用信息等
  • 位置:元空间(Metaspace)

在 JDK 8 中,方法区(Method Area)的实现是“元空间”(Metaspace),它使用本地内存(Native Memory),不再属于 JVM 堆。

所以:运行时常量池 → 方法区 → 元空间


2. 字符串常量池

  • 存储:字符串字面量(如 "hello"
  • 示例:
    String s = "hello"; // "hello" 存在字符串常量池中
  • 位置:堆(Heap)

这是 JDK 7 开始的改变!

  • JDK 6 及之前:字符串常量池在 永久代(PermGen)
  • JDK 7:字符串常量池被移到
  • JDK 8:继续在 ,而永久代被彻底移除,由 元空间 取代

为什么字符串常量池要放在堆里?

  1. 永久代垃圾回收效率低,而字符串大量使用,容易造成内存问题。
  2. 堆的垃圾回收更高效,字符串常量池放在堆里更利于回收不再使用的字符串。
  3. 元空间使用本地内存,主要用于存储类的元数据(如类结构、方法字节码等),不适合放大量字符串。

示例验证

public class ConstantPoolTest {
    public static void main(String[] args) {
        String s1 = "hello"; // 字符串常量池  
        Integer i = 100;     // Integer 缓存池  堆(但缓存机制不同)
        
        Class<?> clazz = ConstantPoolTest.class;
        // 运行时常量池中的类名、方法名等  元空间
    }
}
  • "hello"(字符串常量池)
  • ConstantPoolTest.class 的类名、方法名等符号 → 元空间(运行时常量池)

再次总结

JDK 8 中:

  • 运行时常量池(类的常量信息)在 元空间(Metaspace)(属于方法区);
  • 字符串常量池堆(Heap)

所以不能简单说“常量池在堆”或“在方法区”,要区分类型。

案例说明

    public static void main(String[] args) {
        String a = new StringBuilder("yunze").toString();
        String b = "yunze";
        String c = a.intern();
        System.out.println(a == b); // false
        System.out.println(a == c); // false
        System.out.println(b == c); // true
    }

String中的intern方法是一个 native 的方法,当调用 intern方法时,如果池已经包含一个等于此String对象的字符串(用equals(oject)方法确定),则返回池中的字符串。否则,将intern返回的引用指向当前字符串 s1。

变量来源指向的对象位置说明
anew StringBuilder("yunze").toString()StringBuilder.toString() 创建的全新对象,与常量池无关。
b"yunze"字符串常量池是字符串字面量,直接来自常量池。
ca.intern()字符串常量池intern() 方法拿到的是常量池里对象。