方法区大小设置与 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 MyApp1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
MetaspaceSize vs MaxMetaspaceSize
这两个参数的关系和 -Xms / -Xmx 类似:
bash
# MetaspaceSize = 初始大小(触发 GC 的阈值)
# MaxMetaspaceSize = 上限(防止无限扩张)
# 如果只设置 MetaspaceSize,不设置 MaxMetaspaceSize
# 元空间会无限增长直到物理内存耗尽
java -XX:MetaspaceSize=256m MyApp
# 效果:初始 256MB,按需增长,无上限(危险)
# 推荐设置:同时设置初始值和最大值
java -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m MyApp1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
元空间的使用计算
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=压缩类空间已使用1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
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;
}
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
java -XX:MetaspaceSize=64m -XX:MaxMetaspaceSize=128m MetaspaceOOM
# ...
Generated 5000 classes
Generated 6000 classes
java.lang.OutOfMemoryError: Metaspace1
2
3
4
5
2
3
4
5
案例二:压缩类空间耗尽
当元空间达到 CompressedClassSpaceSize 时,即使元空间本身没满,也会 OOM:
java
public class CompressedClassOOM {
public static void main(String[] args) {
// 如果使用大量类(特别是用反射和动态代理)
// 可能耗尽压缩类空间
// 默认压缩类空间只有 1GB
// 设置较小值模拟:
// java -XX:CompressedClassSpaceSize=256m
}
}1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
案例三:反射和动态代理的元空间压力
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++;
}
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
排查工具
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> 10001
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
元空间调优建议
| 场景 | 建议 |
|---|---|
| 标准 Web 应用 | 元空间默认自动增长,通常不需要手动设置 |
| 大量使用反射/Spring | -XX:MetaspaceSize=256m |
| OSGi/热部署/动态类生成 | -XX:MaxMetaspaceSize=1g |
| 类数量可预估 | 设置合理的 MaxMetaspaceSize,防止失控 |
| 容器环境 | 设置 MaxMetaspaceSize,避免占用过多内存 |
本节小结
方法区(元空间)配置的核心参数:
| 参数 | 说明 |
|---|---|
-XX:MetaspaceSize | 初始大小,触发 GC 和扩容的阈值 |
-XX:MaxMetaspaceSize | 最大大小,防止无限扩张 |
-XX:CompressedClassSpaceSize | 压缩类指针空间大小 |
OOM 的主要原因:加载了太多类(反射、动态代理、CGLib、OSGi 等场景)。
下一节,我们来看 方法区内部结构(常量池/运行时常量池)。
