java 8以前permgen溢出主因是类元数据超固定上限(32位64mb/64位82mb),非堆内存不足;应设合理-xx:permsize与-xx:maxpermsize参数并配合cms类卸载,而非盲目扩容。

Java 8 以前的版本中,永久代(PermGen)是存放类元数据、常量池、静态变量等信息的区域。它大小固定、无法自动扩容,一旦加载的类过多(如频繁热部署、大量动态代理、第三方 SDK 自带类加载器),就容易触发 java.lang.OutOfMemoryError: PermGen space。
这不是堆内存不足,而是类元数据“撑爆”了这块固定空间。解决思路不是盲目加内存,而是合理设置上限 + 控制类加载行为 + 避免泄漏。
明确 PermGen 的默认限制和关键参数
- 默认大小:32 位 JVM 约 64MB,64 位 JVM 约 82MB
- 核心可调参数只有两个:
-
-XX:PermSize=N:初始永久代大小(JVM 启动时分配) -
-XX:MaxPermSize=N:永久代最大允许大小(不可超过此值)
-
⚠️ 注意:这两个参数仅在 JDK 7 及更早版本有效;JDK 8+ 已移除 PermGen,改用 Metaspace,继续使用会直接报错
Unrecognized VM option 'MaxPermSize'。
如何通过参数缓解 PermGen 溢出
-
设一个合理上限,而非越大越好
过大的-XX:MaxPermSize会延迟 GC 触发,反而让泄漏更隐蔽,且 Full GC 时间变长。建议根据应用实际类数量调整:- 普通 Web 应用(Spring MVC + 若干 jar):
-XX:MaxPermSize=128m~256m - 大型项目或含大量插件/脚本引擎(Groovy、JRuby):可试
512m,但需配合监控验证 - 初始值
-XX:PermSize建议设为上限的 1/2~2/3,减少启动期扩容开销,例如:-XX:PermSize=128m -XX:MaxPermSize=256m
- 普通 Web 应用(Spring MVC + 若干 jar):
-
必须配合 GC 行为观察,不能只调参数
加了参数后仍溢出,说明类在持续增长且未卸载。此时要检查:- Tomcat 下是否反复
reload应用?每次 reload 都会生成新 ClassLoader 和一批新类,旧类若被强引用就无法回收 - 是否用了 CGLIB 动态代理(如 Spring AOP 默认)、Hibernate/JPA 的字节码增强?每个代理类都算一个新类
- 第三方 SDK(如某些监控 agent、日志埋点工具)是否在 shutdown 时未清理其 ClassLoader?
- Tomcat 下是否反复
-
辅助手段:启用类卸载支持(部分 JVM 支持)
虽然 HotSpot 默认不主动卸载类,但可尝试添加:-XX:+CMSClassUnloadingEnabled -XX:+UseConcMarkSweepGC
(仅适用于使用 CMS 垃圾收集器的 JDK 7 及更早版本)
它能让 CMS GC 在并发标记阶段尝试卸载无用类——前提是对应 ClassLoader 已不可达。这是治标之策,依赖框架正确释放引用。
实际配置示例(Tomcat 7 + JDK 7)
修改 TOMCAT_HOME/bin/catalina.sh(Linux)或 catalina.bat(Windows),在 JAVA_OPTS 中加入:
你接手一个需求时,是不是经常这样:"改一个小字段 → 结果把不相关的代码也改了"、"需求说得很模糊 → 做着做着发现跑偏了"、"数据库加个字段 → 上线才发现漏了数据兼容"、"做了个新功能 → 一个月后自己都忘了当时怎么设计的"、"写到一半被中断 → 再回来不知道从哪继续";这个 Skill 就是专门解决这些问题的。 它会自动探测你的项目环境(JDK、Spring Boot、ORM、数据库、前端框架),记在
JAVA_OPTS="$JAVA_OPTS -XX:PermSize=128m -XX:MaxPermSize=256m -XX:+CMSClassUnloadingEnabled"
✅ 推荐搭配
-Xms/-Xmx固定堆大小(如-Xms1g -Xmx1g),避免堆伸缩干扰 PermGen GC 判定。
如果调参后仍频繁 OOM,说明已超出参数能解决的范畴
这时本质是类泄漏(ClassLoader 泄漏),必须深入分析:
- 用
jmap -histo:live <pid></pid>查看类实例数是否随 reload 持续上涨 - 用
jmap -dump:format=b,file=perm.hprof <pid></pid>抓取内存快照,用 MAT 分析ClassLoader实例及其引用链 - 检查代码中是否有
static final Map<classloader ...></classloader>或监听器注册后未注销等强持有模式
不复杂但容易忽略
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










