方法区(元数据区)存储类结构信息、运行时常量池、静态变量、JIT编译代码及字段方法元数据;其回收依赖类卸载,须同时满足实例全回收、ClassLoader被回收、Class对象无强引用三条件。

方法区(JVM 8+ 中称为元数据区,Metaspace)主要存储类的结构性信息,不是运行时对象数据。它的回收不频繁,但对长期运行、动态加载类多的应用(如OSGi、热部署、字节码生成框架)很关键。
方法区里存的是哪些元数据
它保存的是 JVM 加载一个类后产生的“描述性数据”,包括:
- 类的结构信息:类名、父类、接口、访问修饰符、字段和方法的签名等;
- 常量池(运行时常量池):字符串常量、符号引用(如类/字段/方法名)、数字字面量等;
- 静态变量(static fields):注意是变量本身(引用或基本类型值),不是其指向的对象(对象在堆中);
- 即时编译器(JIT)生成的本地代码:热点方法编译后的机器码,也缓存在方法区(或CodeCache,逻辑上与方法区协同);
- 字段和方法的元数据:比如方法的字节码、异常表、行号表、局部变量表等调试信息(若未用 -g:none 编译则保留)。
方法区垃圾回收的实际触发条件
元数据区本身不直接“满就回收”,而是通过类卸载(class unloading)来释放空间。而类卸载必须同时满足三个硬性条件:
- 该类所有实例均已回收:堆中不存在该类及其子类的任何活跃对象;
- 加载该类的 ClassLoader 已被回收:通常是自定义类加载器(如 WebAppClassLoader)被置为不可达且无引用;
- 该类对应的 java.lang.Class 对象无任何强引用:不能被静态变量、ThreadLocal、JNI 全局引用、反射缓存等持有。
只有当这三个条件全部成立,JVM 才会在下一次 Full GC 或某些特定 GC 策略(如 G1 的并发周期)中,把该类的元数据从元数据区清理掉。
影响元数据区回收的关键配置与行为
元数据区默认使用本地内存(Native Memory),不受堆大小限制,但可通过参数控制:
- -XX:MaxMetaspaceSize:设置元数据区最大容量,超限时会触发 Full GC 尝试卸载类;
- -XX:MetaspaceSize:初始阈值,首次达到后会触发一次 GC(类似年轻代 Eden 满);
- Full GC 是主要入口:CMS、G1、ZGC 等现代收集器中,元数据区回收通常绑定在 Full GC 或并发标记周期末尾执行;
- System.gc() 不保证触发元数据回收:它仅建议 JVM 执行 GC,是否回收方法区取决于当前 GC 策略和条件是否满足。
常见不回收的典型场景
即使类看起来“不用了”,也常因隐式引用导致无法卸载:
- Spring Bean 被 ApplicationContext 强引用,其 Class 无法卸载;
- 使用 Class.forName() 或反射获取的 Class 对象被静态 Map 缓存;
- Log4j、SLF4J 等日志框架内部持有类加载器或 Class 引用;
- 线程局部变量(ThreadLocal)中存有该类的实例或 Class 对象;
- JNI 代码注册了全局引用(GlobalRef)到该类或其实例。

















