遇到Metaspace OOM应先排查动态代理或反射导致的类加载泄漏,通过-XX:+TraceClassLoading/-XX:+TraceClassUnloading日志、jstat -gc监控MU/MC及GC次数、-XX:+PrintGCDetails确认元空间使用情况,定位Spring上下文刷新、细粒度AOP、脚本热编译等高危场景,优先采用JDK代理、限制代理范围、ClassLoader隔离复用及显式卸载等手段控量卸载,合理设置-XX:MaxMetaspaceSize和-XX:MetaspaceSize。

遇到 java.lang.OutOfMemoryError: Metaspace,别急着加内存,重点要查“谁在不停往图纸库里塞新设计图”——也就是哪些动态代理类或反射行为正在疯狂加载类且不释放。
确认是不是动态代理/反射真在撑爆元空间
靠猜没用,得看证据:
- 启动时加上 -XX:+TraceClassLoading -XX:+TraceClassUnloading,观察日志里是否高频出现
com.sun.proxy.$Proxy*、net.sf.cglib.proxy.$EnhancerBySpringCGLIB$*或org.springframework.cglib.core.$KeyFactoryBySpringCGLIB$*这类命名,同时Unloaded行极少甚至没有 - 运行中执行 jstat -gc
,紧盯 MU(Metaspace Used) 和 MC(Metaspace Capacity):如果 MU 持续上涨、MGCC(Metaspace GC 次数)长期为 0,基本坐实类堆积 - 加 -XX:+PrintGCDetails 启动,GC 日志里会明确打印类似
Metaspace used 245760K, capacity 246144K的行,对比你设的 -XX:MaxMetaspaceSize 是否已逼近或突破
定位高频生成代理类的源头
常见高危场景有这几类:
-
Spring Boot 测试中反复刷新上下文:比如单元测试里频繁 new
AnnotationConfigApplicationContext(),每次都会重建容器并生成全新代理类 -
AOP 配置粒度太细:切面表达式写成
@Within(*)或对大量工具类、第三方包开启代理,导致 CGLIB 为每个匹配类都生成增强子类 - Groovy/JSR-223 脚本热编译:每次脚本变更都触发重新解析、生成 Class,且 ClassLoader 往往未被回收
-
反射调用 + 动态类生成混用:如用
MethodHandle或Lookup.findSpecial结合 Javassist 生成适配器类,又没做缓存
针对性解决思路:不是扩容,而是控量与卸载
核心是让类能复用、延迟生成、或及时卸载:
- 把 CGLIB 代理换成 JDK 动态代理(
Proxy.newProxyInstance),前提是目标类实现了接口;JDK 代理只生成一个通用$ProxyN类,而 CGLIB 为每个被代理类都建独立子类 - Spring AOP 中显式关闭不必要的代理:在
@EnableAspectJAutoProxy(proxyTargetClass = false)强制走接口代理;或用@Scope("prototype")配合懒加载减少早期代理生成 - 对脚本引擎做 ClassLoader 隔离与复用:避免每次执行都新建
URLClassLoader,改用共享的、可卸载的自定义 ClassLoader,并在脚本失效后显式调用close() - 检查是否有类加载器泄漏:用 MAT 打开 heap dump,按 ClassLoader 分组查看 loaded class 数量,特别关注未被 GC 的自定义 ClassLoader 实例
参数设置要合理,不能只靠堆参数
Metaspace 不受 -Xmx 控制,必须单独调:
- 生产环境务必设置 -XX:MaxMetaspaceSize=256m(中小应用)或 512m(Spring Cloud 微服务),防止吃光系统内存
- 搭配 -XX:MetaspaceSize=128m,让 JVM 在达到该阈值时尽早触发 Metaspace GC,而不是等到快爆才行动
- 禁用 -XX:MinMetaspaceFreeRatio 等冷门参数,它们对实际回收效果微乎其微,反而干扰判断
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











