Java 6及之前String常量池位于永久代,大量intern()调用易引发OutOfMemoryError: PermGen space;JDK 7起移至堆,JDK 8+改用元空间,问题本质随版本演进而变化。

Java 6 及之前版本中,String 常量池确实位于永久代(PermGen),大量动态生成的字符串(如通过 String.intern())可能填满 PermGen,引发 java.lang.OutOfMemoryError: PermGen space。但需注意:从 Java 7 开始,字符串常量池已移至堆内存;Java 8 彻底移除 PermGen,改用元空间(Metaspace)。因此该问题仅适用于 Java 6 或早期 Java 7(未开启字符串去重优化)环境。
确认 JDK 版本和内存模型
先明确运行环境,避免误判:
- 执行
java -version,确认是 Java 6 或 Java 7u40 之前版本 - 检查 JVM 启动参数是否含
-XX:PermSize和-XX:MaxPermSize - 若用 Java 8+,出现类似错误实际是
Metaspace溢出,错误信息会是OutOfMemoryError: Metaspace,与 String 常量池无关
定位是否为 String.intern() 导致
常见诱因是代码中频繁调用 String.intern(),尤其在循环或处理大量唯一字符串时(如解析 CSV、日志标签、URL 路径等):
- 搜索代码中
.intern()调用,特别关注高并发或批量处理逻辑 - 用 JFR(Java Flight Recorder)或 JVisualVM + Visual GC 插件监控 PermGen 使用趋势,观察其随请求增长而持续上升
- 触发 OOM 后生成 heap dump(加
-XX:+HeapDumpOnOutOfMemoryError),用 Eclipse MAT 分析:按java.lang.String的retained size排序,查看是否有大量相同内容的 interned 字符串实例
验证常量池占用(Java 6/7)
可通过 JVM 内置工具获取常量池统计信息:
立即学习“Java免费学习笔记(深入)”;
- 启用详细 GC 日志:
-XX:+PrintGCDetails -XX:+PrintGCTimeStamps,观察 Full GC 是否频繁且 PermGen 回收无效 - 使用
jstat -permstat <pid>(Java 6)或jstat -gc <pid>(Java 7),重点关注PC(Permanent Capacity)、PU(Permanent Used)列是否趋近上限 - 对运行中的进程执行
jmap -histo:live <pid> | grep java.lang.String,结合jmap -permstat <pid>(Java 6)查看类加载器与 interned 字符串关联情况
修复与规避方案
核心思路是减少不必要的 intern,或升级/调整运行环境:
- 删除非必要
.intern()调用;若用于字符串比较优化,改用equals()(现代 JVM 对字符串比较已高度优化) - 若必须缓存字符串,改用
ConcurrentHashMap<String, String>自管理,可控清理(如 LRU 策略) - 临时缓解:增大 PermGen(
-XX:MaxPermSize=256m),但治标不治本 - 根本解决:升级到 Java 7u40+(常量池在堆中,受 GC 管理)或 Java 8+(无 PermGen,元空间默认自动扩容)
不复杂但容易忽略:很多老项目沿用 Java 6 容器(如 WebLogic 10.3.6),排查时务必先确认 JDK 版本和错误发生时的实际内存区域归属。



















