常量池溢出本质是方法区(JDK 7前为永久代,JDK 8+为元空间)中类元数据和运行时常量存储区域被填满,导致JVM无法写入新内容而抛出OutOfMemoryError;其主因包括字符串常量池被大量intern()占满(JDK 6尤甚)、动态生成类过多(如CGLib代理)、类加载器泄漏致类无法卸载,以及元空间配置过小或未设上限。

常量池溢出本质是方法区(JDK 7 及之前叫永久代,JDK 8+ 叫元空间)中用于存储类元数据和运行时常量的区域被填满,导致 JVM 无法再写入新内容而抛出 OutOfMemoryError: PermGen space(JDK 7 及以前)或 OutOfMemoryError: Metaspace(JDK 8+)。
字符串常量池被大量 intern() 占满
在 JDK 6 及以前,字符串常量池位于永久代中,容量固定。调用 String.intern() 会将字符串对象强制放入常量池,且只要池中存在就不会重复添加;但如果持续生成新字符串并 intern(如 String.valueOf(i++).intern()),常量池很快耗尽。
- JDK 6:直接触发
PermGen spaceOOM - JDK 7:字符串常量池已移至堆内存,该操作不再导致方法区溢出,但可能引发堆溢出
- JDK 8+:字符串常量池在堆中,
intern()不再影响元空间,但若配合大量动态类加载,仍可能间接加剧元空间压力
动态生成类过多(如 CGLib、ASM、Javassist)
框架如 Spring AOP 默认使用 CGLib 创建代理类,每次生成一个新代理类,就会向元空间注册一份完整的类元数据(类名、字段、方法签名、字节码等)。这类类通常由非系统类加载器加载,且难以卸载。
- 高频 RPC 调用、反复开启事务代理、自定义注解处理器扫描时生成类,都容易积累
- 尤其在热部署场景(如 DevTools、Tomcat reload)中,旧类加载器未被回收,其加载的所有类元数据仍驻留元空间
类加载器泄漏 + 未卸载类
每个类加载器都维护一份独立的类元数据视图。如果 Web 应用重启后,老的 WebAppClassLoader 实例仍被静态引用、线程上下文、JDBC 驱动或第三方 SDK 持有,它加载的所有类就无法被卸载,元空间占用只增不减。
立即学习“Java免费学习笔记(深入)”;
- 典型表现:应用重启多次后,Metaspace 使用量持续攀升,即使业务无变化
- JVM 不会主动卸载类,除非其类加载器可被 GC —— 而泄漏的加载器永远不可达回收条件
元空间配置过小或未设上限
元空间默认使用本地内存,理论上只受物理内存限制。但生产环境必须显式设置 -XX:MaxMetaspaceSize,否则可能因无限扩张拖垮系统;同时若设得过小(如仅 64MB),正常规模的微服务也可能在加载 Spring Boot 全家桶后迅速触顶。
- 建议值:普通 Spring Boot 应用建议 ≥128MB;含大量模块/插件的平台级应用建议 ≥256MB~512MB
- 搭配监控:通过
-XX:+PrintGCDetails或 JMX 查看MetaspaceUsed和MetaspaceCapacity实时变化


















