结论是类加载失控导致metaspace溢出,而非内存配置不足;需通过日志确认java.lang.outofmemoryerror: metaspace、jstat -gc查mu/mc趋势、jcmd vm.native_memory验证class占用,并定位classloader泄漏或动态类疯长根源。

直接结论:这不是“内存小”,而是类加载器没卸载、动态类疯长或类加载器泄漏。加-XX:MaxMetaspaceSize只能压住报错,不能阻止崩溃。
怎么确认真是 Metaspace 溢出,不是堆或别的地方炸了
只看日志里有没有明确的 java.lang.OutOfMemoryError: Metaspace —— 其他任何 OOM(比如 Java heap space 或 Direct buffer memory)都别往这儿套。
运行中验证必须用三组命令交叉比对:
-
jstat -gc <pid></pid>:盯紧MU(Metaspace Used)和MC(Metaspace Capacity),如果MU持续上涨、逼近甚至超过你设的MaxMetaspaceSize,就是真满了 -
jcmd <pid> VM.native_memory summary</pid>:需启动时加-XX:NativeMemoryTracking=summary;重点看输出里Class行的占用,几百 MB 以上基本坐实 - 查 GC 日志(加了
-XX:+PrintGCDetails):找含Metaspace used ... capacity ... committed的行,对比used和你设的上限值
常见真实原因和立刻能做的动作
Metaspace 不是被“撑爆”的,是类加载后死赖着不走。以下场景在 OpenJDK 8–21 中高频复现:
-
Spring AOP + CGLIB 动态代理泛滥:
$EnhancerBySpringCGLIB$类持续增长,jstat -class <pid></pid>显示 loaded 类数涨、unloaded 几乎为 0 → 改配置proxyTargetClass=false,或检查是否在循环里反复 newAnnotationConfigApplicationContext - 热部署/重加载泄漏:Tomcat 多次 redeploy、Spring Boot DevTools 生产启用 → 禁用 DevTools,或改用支持类加载器显式回收的容器(如 Quarkus 的 dev-mode)
-
Groovy/JSR-223 脚本引擎每次执行都 new 实例:比如
new GroovyShell()或ScriptEngineManager.getEngineByName("groovy")→ 必须复用脚本实例,缓存编译后的CompiledScript -
多个版本相同 jar 被不同 ClassLoader 加载:比如
logback-classic:1.2.11和1.4.14并存 → 运行mvn dependency:tree -Dverbose扫描冲突,用<exclusions></exclusions>排掉冗余传递依赖
参数怎么设才安全有效
OpenJDK 默认不限制 Metaspace,靠本地内存硬扛,一满就崩,不 GC。生产必须设限,但数值不是越大越好:
-
-XX:MaxMetaspaceSize=256m:中小服务起步值;Spring Cloud 微服务建议512m;绝对不要留空或设成0 -
-XX:MetaspaceSize=128m:这是首次触发 Metaspace GC 的阈值,不是初始分配量;设太小(如32m)会导致启动期频繁 GC,设太大(如400m)会拖到快爆才回收 -
-XX:+UseG1GC -XX:+ClassUnloading:G1 是 OpenJDK 8u212+ 及 11+ 默认 GC,ClassUnloading必须显式开(旧版 G1 默认关),否则无用类元数据根本不会被回收 - 别碰
-XX:MinMetaspaceFreeRatio和-XX:MaxMetaspaceFreeRatio:默认值(40/70)已足够稳,调它既难见效,又干扰 GC 行为判断
最常被忽略的致命点
很多人 dump 后用 MAT 看 java.lang.Class 对象多,就以为是“类太多”,其实关键在 ClassLoader 实例 —— 如果 sun.misc.Launcher$AppClassLoader 或自定义 URLClassLoader 实例数持续增长,说明类加载器本身泄漏了,所有它加载的类都永远无法卸载。这时调参毫无意义,必须定位哪个模块没释放 ClassLoader 引用,比如未关闭的插件上下文、未注销的 OSGi Bundle、或 Tomcat webapp reload 后残留的线程上下文类加载器(Thread.currentThread().setContextClassLoader(null) 没做)。











