JDK 8 后 PermGen 被彻底移除,类元数据改存 Metaspace(本地内存),故 OutOfMemoryError: PermGen space 不再出现;但会触发新的 OutOfMemoryError: Metaspace,需通过 -XX:MaxMetaspaceSize 等参数调优,并警惕类加载器泄漏。

为什么 java.lang.OutOfMemoryError: PermGen space 在 JDK 8 后彻底消失
不是错误被“屏蔽”了,而是根本没这个内存区域了——PermGen 被整个移除,类元数据改存到本地内存(native memory),所以那个经典报错再也不会出现。但别高兴太早:它被 java.lang.OutOfMemoryError: Metaspace 替代了,只是触发条件和调优逻辑完全不同。
- PermGen 是堆内固定大小区域,靠
-XX:MaxPermSize硬限制,超了就炸,且无法自动收缩 - Metaspace 默认无上限,只受本机可用内存约束;一旦触及
-XX:MaxMetaspaceSize才会触发 GC 或 OOM - Web 应用热部署、Spring Boot DevTools、大量动态代理(如 CGLIB)等场景,以前是 PermGen 的重灾区,现在压力转到了 Metaspace,但更“耐扛”也更隐蔽
-XX:MetaspaceSize 和 -XX:MaxMetaspaceSize 怎么设才不翻车
这两个参数不是“必须配”,但生产环境强烈建议显式设置 -XX:MaxMetaspaceSize,否则 Metaspace 可能无限膨胀,吃光系统内存,连带影响其他进程甚至触发 OS OOM Killer。
-
-XX:MetaspaceSize是首次触发 Metaspace GC 的阈值(类似新生代的初始大小),不是最小值;设太小会导致频繁 GC,设太大则延迟回收,浪费内存 -
-XX:MaxMetaspaceSize必须设,推荐从 64m–256m 起步,根据实际类数量调整(如 Spring Cloud 微服务常需 128m+) - 注意:
-XX:PermSize和-XX:MaxPermSize在 JDK 8+ 完全无效,启动时会警告,务必删掉
Metaspace GC 是怎么工作的?和老年代 GC 什么关系
Metaspace 的垃圾回收不依赖 Full GC 触发,而是在类加载器卸载(class unloading)时顺带清理其关联的元数据。但关键前提是:该类加载器本身得能被 GC 掉——这恰恰是大多数内存泄漏的根源。
- 只有当某个 ClassLoader 对象变成垃圾(无任何强引用),且它加载的所有类都不再被使用,JVM 才会在下一次 GC 周期中卸载这些类,并释放对应 Metaspace 内存
- 常见泄漏模式:线程局部变量持有 ClassLoader、静态 Map 缓存了 Class 或 ClassLoader 实例、OSGi / Tomcat 热部署未清理监听器
- 监控要看
jstat -gc <pid>中的MU(Metaspace Used)和MC(Metaspace Capacity),持续上涨不回落,基本就是类加载器泄漏了
JDK 8 升级后堆内存突然变高,是不是 Metaspace “偷”走了空间
不是偷,是挪——字符串常量池(StringTable)在 JDK 7 就已从 PermGen 移到堆里,JDK 8 进一步把所有运行时常量池(包括符号引用、类名、方法名等)都留在堆中,而仅把 klass 结构、方法元数据等放 Metaspace。结果就是:堆占用比 JDK 7 明显上升,尤其大量反射或动态生成类的应用。
- 别急着调大堆,先确认是不是常量池膨胀:用
jmap -histo:live <pid>看java.lang.String实例数和总大小 - 如果堆增长伴随 Metaspace 使用平稳,大概率是应用自身行为变化(比如新增了大量枚举、注解处理器、Lombok 生成的类)
- Metaspace 用的是 native memory,不参与 JVM 堆 GC,所以堆 GC 日志里看不到它的动静,这点容易误判

















