常量池
常量池详解
Class常量池
Class文件常量池又称为静态常量池。Class文件常量池就像是编译后.class文件里自带的一份物料清单和索引目录。它主要包含两部分:一类是字面量,比如具体的字符串数字;另一类是符号引用,比如它用java/lang/String的全名来指代String这个类,用println:(Ljava/lang/String;)V指代println这个方法。JVM在加载类的时候,会把这些符号翻译成实际的内存地址,这个过程就叫解析。
- 位置:存储在.class文件中(磁盘上的二进制文件),是静态的、不可变的。
- 内容:
- 字面量(Literal):如字符串"Hello"、数字123、final常量等
- 符号引用(Symbolic References):类/接口的全限定名、字段名和描述符、方法名和描述符
- 关键点:
- 是编译期确定的静态数据,属于Class文件的一部分
- 不占用运行时内存,仅作为JVM加载类的元数据参考
运行时常量池
运行时常量池是Class常量池在类加载到 JVM 后的一种表现形式。它是类加载过程的一部分,每个被JVM加载的类或接口,都会在方法区中生成一个对应的运行时常量池。
- 位置:方法区(元空间Metaspace,JDK8+)
- 内容:
- 包含静态常量池的所有内容(字面量、符号引用)
- 运行时解析后的直接引用(如类、方法、字段的实际内存地址)
- 关键点:
- 符号引用→直接引用的解析过程发生在类加载的解析阶段
- 动态性:运行时可添加新的常量(如String.intern()方法),区别于静态常量池的不可变性
字符串常量池
JVM为了提高性能和减少内存开销,在实例化字符串常量的时候,为字符串开辟一个字符串常量池,类似于缓存区。
字符串常量池底层是 HotSpot的C++实现的,它是一个StringTable(类似java中的 HashTable),保存的本质上是字符串对象的引用。
位置:
Jdk1.6及之前:方法区被实现为永久代,运行时常量池在永久代,运行时常量池包含字符串常量池;
Jdk1.7:已经逐步开始“去永久代”,字符串常量池和静态变量被移到了堆内存中;
Jdk1.8及之后:永久代被移除,取而代之的是元空间。而字符串常量池里依然在堆内存中;- JDK6及之前:方法区(永久代PermGen)
- JDK7+:移至堆内存(Heap)
作用:
- 专门存储字符串字面量和intern()方法返回的字符串引用
- 避免重复创建字符串对象,节省内存
特点:
- 全局共享,所有线程共用同一池
- 使用String.intern()方法可主动将字符串加入池中
| 维度 | 静态常量池/Class文件常量池 | 运行时常量池 | 字符串常量池 |
|---|---|---|---|
| 存储位置 | Class文件 | 方法区(元空间) | 堆内存(JDK7+) |
| 内容 | 字面量、符号引用 | 字面量、直接引用 | 仅字符串对象引用 |
| 生命周期 | 编译期生成 | 类加载时生成,随类卸载回收 | 运行时动态维护 |
| 动态性 | 静态不可变 | 可动态添加(如intern()) | 可动态添加 |
简单来说:
静态常量池是 “源代码” ,存在于磁盘文件里。
运行时常量池是 “进程” ,是JVM将“源代码”加载到内存后执行的结果
String s = new String("abc")创建了几个对象?
若字符串常量池无"abc":先在池中创建字面量对象,再在堆中创建new String对象,共2个
若池中已有"abc":仅堆中创建1个对象
intern()方法的作用?
- 将字符串对象引用加入字符串常量池(若池中不存在),并返回池中引用
为什么JDK7将字符串常量池移到堆中?
- 避免永久代内存溢出(如大量intern()操作),且堆内存更易扩展
字符串常量池的数据结构?
- 本质是哈希表(类似HashMap),JDK7后改用更高效的StringTable实现
总结
从JDK1.7开始,字符串常量池被移动了堆内存中;
字面量赋值,会先去字符串常量池中判断是否存在相同值的字符串对象,存在则返回常量池中的引用,不存在则在常量池中创建一个新对象,并返回引用;
使用new关键字创建字符串对象,会先去字符串常量池中判断是否存在相同值的字符串对象,存在则去堆中创建一个新对象并返回;不存在也在字符串常量池和堆内存中各创建一个字符串对象,并返回堆内存中的对象引用;
常量之间的连接,Java编译器在编译期间会做优化;
连接时含有变量的,会被编译成(new StringBuilder().append(a).append(b).append(c).toString();
当调用intern(时,首先判断常量池中是否存在相同值的字符串常量,如果存在则返回该字符串的引用;如果不存在,则在常量池中增加一个相同值的字符串常量,并返回它在堆内存中的引用;
对于final修饰的变量,它在编译时被解析为常量值的一个本地拷贝存储到自己的常量池中或嵌入到它的字节码流中;
Java中的部分关键字在初始化的类中可能被提前使用而放入常量池。
String s = new String("hello"); //1.字符串常量池中放入了一个“hello” 2.在堆内存中创建了一个String对象存储了“hello”,使用s指向了这个对象引用 String s1 = s.intern(); //s1指向的是字符串常量池中“hello”的引用地址 String s2 = "hello"; //s2指向的是字符串常量池中“hello”的引用地址 System.out.println( s == s2); //false System.out.println( s1 == s2); //true
