Skip to content

方法区大小设置与 OOM 案例

方法区大小设置

JDK 8+ 的方法区(元空间)使用本地内存,不再受堆大小限制。但仍然需要合理配置。

核心参数

bash
# JDK 8+ 元空间参数

# 初始元空间大小(达到这个值会触发 GC 和扩容)
java -XX:MetaspaceSize=256m MyApp

# 最大元空间大小(默认为无限制,直到物理内存耗尽)
java -XX:MaxMetaspaceSize=512m MyApp

# 压缩类指针(默认开启,减少类元数据内存占用)
java -XX:+UseCompressedClassPointers MyApp

# 类指针压缩空间大小(默认 1GB)
java -XX:CompressedClassSpaceSize=1g MyApp

MetaspaceSize vs MaxMetaspaceSize

这两个参数的关系和 -Xms / -Xmx 类似:

bash
# MetaspaceSize = 初始大小(触发 GC 的阈值)
# MaxMetaspaceSize = 上限(防止无限扩张)

# 如果只设置 MetaspaceSize,不设置 MaxMetaspaceSize
# 元空间会无限增长直到物理内存耗尽
java -XX:MetaspaceSize=256m MyApp
# 效果:初始 256MB,按需增长,无上限(危险)

# 推荐设置:同时设置初始值和最大值
java -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m MyApp

元空间的使用计算

bash
# 查看元空间使用情况(JDK 8)
jstat -gc <pid>

# 输出:
# S0C  S1C  S0U  S1U   EC    EU    OC     OU     MC     MU     CCSC  CCSU   YGC  YGCT
# ...   ...  ...  ...   ...   ...   ...   ...  21504.0 20964.0  3072.0 2560.0  ...
#                                                    MC=21504K=元空间总容量
#                                                    MU=20964K=元空间已使用
#                                                    CCSC=3072K=压缩类空间总容量
#                                                    CCSU=2560K=压缩类空间已使用

OOM 实战案例

案例一:元空间耗尽(JDK 8+)

元空间 OOM 的原因通常是加载了太多类

java
public class MetaspaceOOM {
    public static void main(String[] args) {
        // 模拟不断加载新的类
        int count = 0;
        try {
            while (true) {
                // 使用 CGLib 动态生成类
                Enhancer enhancer = new Enhancer();
                enhancer.setSuperclass(MetaspaceOOM.class);
                enhancer.setCallback((MethodInterceptor) (obj, method, args1, proxy) -> proxy.invokeSuper(obj, args1));
                enhancer.create();
                count++;
                if (count % 1000 == 0) {
                    System.out.println("Generated " + count + " classes");
                }
            }
        } catch (Error e) {
            System.out.println("OOM after generating " + count + " classes");
            throw e;
        }
    }
}
java -XX:MetaspaceSize=64m -XX:MaxMetaspaceSize=128m MetaspaceOOM
# ...
Generated 5000 classes
Generated 6000 classes
java.lang.OutOfMemoryError: Metaspace

案例二:压缩类空间耗尽

当元空间达到 CompressedClassSpaceSize 时,即使元空间本身没满,也会 OOM:

java
public class CompressedClassOOM {
    public static void main(String[] args) {
        // 如果使用大量类(特别是用反射和动态代理)
        // 可能耗尽压缩类空间
        // 默认压缩类空间只有 1GB
        // 设置较小值模拟:
        // java -XX:CompressedClassSpaceSize=256m
    }
}

案例三:反射和动态代理的元空间压力

Spring、Hibernate、MyBatis 等框架大量使用反射和动态代理,会生成大量代理类:

java
public class ReflectionOOM {
    public static void main(String[] args) throws Exception {
        // JDK 动态代理每次 newProxyInstance 都生成新类
        // CGLib 每次 Enhancer.create() 生成新类
        int count = 0;
        while (true) {
            Object proxy = Proxy.newProxyInstance(
                ReflectionOOM.class.getClassLoader(),
                new Class[]{Runnable.class},
                (p, method, methodArgs) -> null
            );
            count++;
        }
    }
}

排查工具

bash
# 1. 查看元空间详情
jmap -clstats <pid>

# 2. 查看类加载信息
jcmd <pid> GC.class_histogram | head -30

# 3. 生成堆转储(OOM 时)
java -XX:+HeapDumpOnOutOfMemoryError \
     -XX:HeapDumpPath=/tmp/ \
     -XX:MetaspaceSize=64m -XX:MaxMetaspaceSize=128m MyApp

# 4. 使用 jstat 监控元空间
jstat -gcutil <pid> 1000

元空间调优建议

场景建议
标准 Web 应用元空间默认自动增长,通常不需要手动设置
大量使用反射/Spring-XX:MetaspaceSize=256m
OSGi/热部署/动态类生成-XX:MaxMetaspaceSize=1g
类数量可预估设置合理的 MaxMetaspaceSize,防止失控
容器环境设置 MaxMetaspaceSize,避免占用过多内存

本节小结

方法区(元空间)配置的核心参数:

参数说明
-XX:MetaspaceSize初始大小,触发 GC 和扩容的阈值
-XX:MaxMetaspaceSize最大大小,防止无限扩张
-XX:CompressedClassSpaceSize压缩类指针空间大小

OOM 的主要原因:加载了太多类(反射、动态代理、CGLib、OSGi 等场景)。

下一节,我们来看 方法区内部结构(常量池/运行时常量池)

基于 VitePress 构建