JVM 元空间溢出(OutOfMemoryError: Metaspace)排查与解决
Metaspace 溢出常被误以为是堆不够,其实根因是类加载器泄漏导致类元数据无法卸载。本文讲清 Heap 与 Metaspace 的区别、类卸载机制,以及 jstat / arthas 排查和治理方法。
一、问题案例
服务频繁热部署,跑几天就报 OutOfMemoryError: Metaspace。第一反应是堆内存不够,加了 -Xmx 也没用,还是挂。
二、原理详解:Metaspace 与类卸载
Metaspace 不在堆里,它存的是类的元数据(类名、方法、字段信息)。
- Heap(堆) 存对象;
- Metaspace(元空间) 存类元数据,两者是分开的。
Metaspace 溢出的典型原因:大量类被反复加载,且旧的类加载器没被回收。
类想被卸载,前提是「类加载器被 GC」。如果类加载器还被引用(线程、静态引用等),类就卸载不了,Metaspace 只增不减。
热部署框架、动态代理、反射生成类、脚本引擎动态编译,都会反复创建 ClassLoader,是 Metaspace 泄漏的高发场景。
三、实战代码:排查与治理
排查:
bash
# 设个上限,避免无限制增长拖垮整个 JVM
-XX:MaxMetaspaceSize=256m
# 观察 Metaspace 用量
jstat -gc <pid> 1000
用 arthas 或 heap dump 定位是哪些 ClassLoader 在堆积:
bash
# arthas 查看类加载器
classloader --list
治理:
- 找到「反复创建 ClassLoader」的源头(热部署、动态代理、反射框架),控制加载频率或复用 ClassLoader;
- 适当调大
-XX:MaxMetaspaceSize只能兜底,治标不治本。
四、常见踩坑
- Metaspace OOM ≠ Heap OOM :加
-Xmx对 Metaspace 没用。 - 频繁动态生成类的框架要警惕:Groovy / JS 脚本引擎、大量动态代理,都是 ClassLoader 泄漏重灾区。
五、总结
- Metaspace 存类元数据,与堆分开,溢出原因也不同。
- 根因是类加载器泄漏导致类无法卸载。
- 用
jstat -gc/ arthas 定位,从源头控制 ClassLoader 创建。
我是无羡(小剑),全栈偏后端的独立开发者。
作品集:无羡 · 独立开发者作品集
如果对你有帮助,欢迎点赞、收藏、关注。