# 50. 什么是类卸载?为什么JDK 8的Metaspace可能导致OOM?
# 标准答案
类卸载(Class Unloading)是 JVM 通过垃圾回收(GC)移除 无引用的类及其元数据,以释放 元空间(Metaspace) 或 永久代(PermGen) 的内存。类卸载通常发生在 Full GC 过程中,前提是对应的 ClassLoader
及其加载的所有类实例 都不可达。
在 JDK 8 之前,类元数据存储在 永久代(PermGen),但其固定大小易引发 OOM。JDK 8 移除了永久代,引入 Metaspace,将类元数据存储在 本地内存(Native Memory),避免 JVM 内部固定大小的限制。然而,Metaspace 并不等于无限大,若未合理配置,仍可能因 类加载过多(如动态代理、热加载)导致 OOM。
# 答案解析
# 1. 什么是类卸载?
类卸载指 JVM 通过 GC 回收无用的类元数据,释放 Metaspace 内存。类卸载的前提是:
- ClassLoader 不再被引用**(通常是自定义
ClassLoader
)。 - ClassLoader 加载的所有类实例都被回收**(即
Heap
中没有存活对象)。 - 类对象本身无存活引用**(如
ThreadLocal
没有持有Class
)。
类卸载通常由 Full GC 触发,GC 日志中 Class unloading
表示发生了类卸载。
[Full GC (Metadata GC Threshold) 512M->256M, 0.5 secs]
Unloading class com.example.MyClass
1
2
2
# 2. JDK 8 及以后,为什么 Metaspace 可能导致 OOM?
JDK 8 移除了 PermGen,引入 Metaspace,将类元数据存放在 本地内存(Native Memory),而不是 JVM 堆(Heap)。但 Metaspace 不等于无限大,仍可能导致 OOM,主要原因包括:
大量动态类加载
- Spring、Hibernate 动态代理(CGLIB、JDK Proxy)生成大量代理类,导致
Metaspace
持续增长。 - 反复 加载/卸载
ClassLoader
,如 Tomcat 频繁热部署,导致元数据无法及时回收。
- Spring、Hibernate 动态代理(CGLIB、JDK Proxy)生成大量代理类,导致
Metaspace 默认不受
-Xmx
限制- PermGen 受
-XX:MaxPermSize
限制,但 Metaspace 默认只受物理内存限制,如果不设置MaxMetaspaceSize
,可能无限增长导致 OOM。
- PermGen 受
类卸载滞后
- Full GC 触发类卸载,而 JVM 可能避免频繁 Full GC,导致 Metaspace 积累大量废弃类,最终 OOM。
# 3. 代码示例
以下代码演示 动态生成大量类,导致 Metaspace OOM:
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
public class MetaspaceOOM {
public static void main(String[] args) {
while (true) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(OOMObject.class);
enhancer.setUseCache(false);
enhancer.setCallback((MethodInterceptor) (obj, method, args1, proxy) -> proxy.invokeSuper(obj, args1));
enhancer.create(); // 创建动态代理类,持续占用 Metaspace
}
}
static class OOMObject {}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
运行参数:
-XX:MaxMetaspaceSize=10M # 限制 Metaspace 仅 10MB
1
错误日志:
java.lang.OutOfMemoryError: Metaspace
1
# 4. 如何优化 Metaspace,避免 OOM?
合理设置
MaxMetaspaceSize
-XX:MaxMetaspaceSize=256M
限制 Metaspace 避免无限增长。-XX:MetaspaceSize=128M
设置初始 Metaspace,防止频繁 GC。
避免类加载泄漏
- 检查 反复创建
ClassLoader
却不卸载 的情况,如 Tomcat 热部署。 - 避免
ThreadLocal
持有Class
,阻止类卸载。
- 检查 反复创建
使用
jmap
、jstat
排查 Metaspacejmap -clstats <pid>
查看类加载信息。jstat -gcmetacapacity <pid>
监控 Metaspace 增长情况。
手动触发 Full GC
System.gc()
不保证触发 类卸载,JVM 可能忽略调用。- 使用
jcmd <pid> GC.run
强制 Full GC,尝试卸载类。
# 深入追问
- 为什么 Metaspace OOM 可能导致整个 JVM 崩溃?
- Spring AOP 生成代理类时,如何避免 Metaspace OOM?
- JDK 11 引入的 Class-Data Sharing(CDS)如何优化 Metaspace?
# 相关面试题
- 为什么 JDK 8 移除了永久代(PermGen)?
- JVM 何时会触发类卸载?如何查看类卸载日志?
- 如何优化 Tomcat 的热部署,避免 Metaspace OOM?