java jdk1.7常量池移到哪去了?

本文探讨了JDK1.7中常量池的位置变动,从方法区移动到了堆中,并通过实验验证了这一变化。此外,文章还提到了JDK1.8中永久代被元空间取代的情况。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

今天模拟了一下常量池的oom,突然发现设置的参数-XX:PermSize=10M -XX:MaxPermSize=10M不管用了,同时发现内存一直在上升,当上升到一个极值就会趋于平稳,然后再过一段时间会报:
Exception in thread “main” java.lang.OutOfMemoryError: GC overhead limit exceeded
这个异常是当GC的时间超过总运行时间的98%才会报的,是为了防止GC占用的时间过长。

模拟常量池源码:

public static void main(String[] args) throws Throwable {
        List<String> list = new ArrayList<String>();
        int i=0;
        while(true){
            list.add(String.valueOf(i++).intern());
        }

    }

那么问题来了,我们从前知道的是常量池是放在Java JVM中的方法区中的,许多人也叫它“永久代”,可以通过-XX:PermSize=20M -XX:MaxPermSize=20M来设置大小,当这个区域内存溢出会报:
Exception in thread “main” java.lang.OutOfMemoryError:PermGen space的内存溢出异常,但是这里却不是那么一回事,反而GC的时间会过长,那么我猜测这个常量池是移动到了java堆中去了,下面测试一下:

首先,源代码不变,增加运行时JVM的参数:
-Xmx20m -Xms20m -XX:-UseGCOverheadLimit,这里的-XX:-UseGCOverheadLimit是关闭GC占用时间过长时会报的异常,然后限制堆的大小,运行程序,果然,一会后报异常:
Exception in thread “main” java.lang.OutOfMemoryError: Java heap space
从上面的异常可以知道我们测试增加的常量都放到了堆中,所以限制堆内存以后,不断增加常量,堆内存会溢出。和我们猜测的一样,同时我也查看了官网发现了官网有说明http://www.oracle.com/technetwork/java/javase/jdk7-relnotes-418459.html
Area: HotSpot
Synopsis: In JDK 7, interned strings are no longer allocated in the permanent generation of the Java heap, but are instead allocated in the main part of the Java heap (known as the young and old generations), along with the other objects created by the application. This change will result in more data residing in the main Java heap, and less data in the permanent generation, and thus may require heap sizes to be adjusted. Most applications will see only relatively small differences in heap usage due to this change, but larger applications that load many classes or make heavy use of the String.intern() method will see more significant differences.
RFE: 6962931

通过自己的实践和官网说明,可以知道java jdk1.7中的常量池确实是移到了堆中,同时在jdk1.8中移除整个永久代,取而代之的是一个叫元空间(Metaspace)的区域,如果想了解更多可以参考:
http://www.oracle.com/technetwork/java/javase/jdk7-relnotes-418459.html
http://blog.youkuaiyun.com/zhyhang/article/details/17246223/

<think>嗯,用户问的是Java中字符串常量池和运行时常量池的区别,这个问题在Java内存模型中确实容易混淆。用户之前已经了解过JVM内存结构的基础知识,现在想深入理解这两个特定区域的差异。 从用户提供的引用资料来看,主要需要厘清三个概念:运行时常量池、字符串常量池以及它们之间的关系。引用[1]和[2]都明确指出字符串常量池是运行时常量池的子集,但存储内容和行为特性有显著区别。 用户可能正在学习JVM内存模型或准备面试,因为这个问题常出现在Java中级面试中。ta特意要求区分这两个概念,说明已经遇到了实际困惑,比如在码中操作字符串时产生的疑问。 需要重点解释三点本质区别: 第一是存储内容差异,运行时常量池存储的是编译期生成的各种符号引用和字面量(不限于字符串),而字符串常量池只缓存String对象引用。这点在引用[2][3]的哈希表描述中体现得很清楚。 第二是生命周期管理,运行时常量池随类加载产生,理论上可被GC回收;字符串常量池的字符串对象虽然也在中,但通过intern()方法会特殊处理。引用[3]提到的JDK1.8位置迁移也很关键,现在字符串常量池里而运行时常量池在元空间。 第三是访问方式,字符串常量池作为全局哈希表存在唯一性约束,运行时常量池则没有。引用[4]关于直接内存的说明需要谨慎处理,避免混淆“方法区实现”和“常量池存储位置”这两个维度。 在组织回答时要特别注意用户提供的引用来源,其中引用[3]关于哈希表结构的描述和JDK版本迁移的信息最权威,应该优先采用。最后用表格对比的方式呈现核心差异,这样用户查阅时最直观。</think>以下是Java中**字符串常量池**与**运行时常量池**的核心区别及关联: --- ### 一、运行时常量池(Runtime Constant Pool) 1. **存储位置** - JDK 1.7之前:方法区(永久- JDK 1.8之后:**元空间(Metaspace)**(使用本地内存)[^3][^4] 2. **存储内容** - 类加载后从Class文件常量池转换而来的**符号引用**(如类名、方法名、字段名) - **字面量**(包括字符串字面量、数值常量等) - **动态添加的常量**(如运行时生成的字符串或通过`String.intern()`添加的字符串)[^1][^4] 3. **特性** - **动态性**:运行期间可新增常量(如反射生成的符号引用)[^1] - **类级作用域**:每个类拥有独立的运行时常量池 - **存储范围广**:包含所有类型的常量(不限于字符串)[^2][^4] --- ### 二、字符串常量池(String Pool) 1. **存储位置** - JDK 1.6及之前:方法区(永久- JDK 1.7之后:**(Heap)**[^3][^4] 2. **存储内容** - **唯一存储字符串对象引用的区域** - 仅缓存通过字面量赋值(如`String s = "abc"`)或`intern()`方法添加的字符串对象[^2][^3] 3. **核心机制** - **哈希表结构**: - Key:字符串内容的哈希值 - Value:字符串对象在中的地址[^3] - **唯一性保证**:相同内容的字符串在池中仅存一份(通过`intern()`复用)[^2] --- ### 三、核心区别总结 | **特性** | 运行时常量池 | 字符串常量池 | |------------------|--------------------------------|--------------------------------| | **存储内容** | 所有常量(符号引用、字面量等) | 仅字符串对象的引用 | | **数据结构** | JVM内部结构(无固定格式) | 哈希表(Key:哈希值, Value:地址) | | **作用域** | 类级别(每个类独立) | 全局唯一 | | **动态性** | 支持运行时动态添加常量 | 仅通过字面量或`intern()`添加 | | **物理位置** | JDK8+:元空间(本地内存) | JDK7+:内存 | --- ### 四、两者关系图解 ```mermaid graph LR A[运行时常量池] -->|包含字符串字面量引用| B(字符串常量池) B -->|实际对象| C[中的字符串对象] D[Class文件常量池] -->|类加载时加载| A E[码中字面量"abc"] -->|首次出现时创建| B F[new String("abc")] -->|显式创建对象| C G[String.intern()] -->|将对象引用加入| B ``` --- ### 关键示例说明 ```java String s1 = "abc"; // 1. 检查字符串常量池,若无则创建新对象加入池 String s2 = new String("abc"); // 2.中创建新对象,不加入池 String s3 = s2.intern(); // 3. 将s2对象引用加入字符串常量池(若池中不存在) System.out.println(s1 == s3); // true:s1和s3指向池中同一对象 ``` --- ### 常见问题解答 1. **为什么JDK1.7将字符串常量池移到中?** - 避免永久内存溢出(如大量动态生成字符串) - 利用的垃圾回收机制自动清理未使用的字符串[^3][^4] 2. **`intern()`方法的作用是什么?** - 将字符串对象引用添加到字符串常量池(若池中不存在该字符串) - 返回池中的引用,实现字符串复用[^2][^3] 3. **字面量赋值 vs `new String()`的区别?** - `String s = "abc"`:直接使用字符串常量池的对象 - `String s = new String("abc")`:在中创建新对象(可能额外在池中创建对象)[^3] ---
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值