一、PermGen与Metaspace的前世今生

在Java 7及之前版本中,JVM使用永久代(PermGen)存储类元数据、静态变量等。很多开发者都遇到过这样的报错:

java.lang.OutOfMemoryError: PermGen space

这是因为PermGen空间默认较小(64MB~82MB),而动态加载大量类时(比如热部署)极易撑爆。

Java 8用元空间(Metaspace)取代了PermGen,关键改进有两点:

  1. 元空间使用本地内存(Native Memory),理论上只受系统内存限制
  2. 引入垃圾回收机制,自动清理无用的类元数据

但别高兴太早!看看这个Java 8的典型错误:

java.lang.OutOfMemoryError: Metaspace

是的,如果没正确配置,Metaspace照样会溢出。

二、Tomcat内存配置实战

场景1:PermGen配置(Java 7)

在Tomcat的catalina.sh中添加:

# 设置初始PermGen为128MB,最大256MB
export JAVA_OPTS="-XX:PermSize=128m -XX:MaxPermSize=256m"

注意:

  • PermSize是初始值
  • MaxPermSize是上限值
  • 建议两者设为相同,避免扩容时的性能抖动

场景2:Metaspace配置(Java 8+)

对于Tomcat 9+,改用以下参数:

# 初始Metaspace 100MB,最大200MB,达到阈值触发FullGC
export JAVA_OPTS="-XX:MetaspaceSize=100m -XX:MaxMetaspaceSize=200m"

关键区别:

  • MetaspaceSize是触发GC的阈值
  • 实际占用可能超过该值,直到达到MaxMetaspaceSize才会OOM

三、避坑指南

陷阱1:动态类加载

使用JRebel等热部署工具时,建议将Metaspace上限调高:

# 开发环境可适当放宽限制
-XX:MaxMetaspaceSize=512m

陷阱2:框架依赖

Spring+Hibernate应用往往需要更大空间:

# 典型企业级配置
-XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m

陷阱3:Docker环境

在容器中运行时,必须显式设置:

# 防止JVM误用宿主机的全部内存
-XX:MaxRAMPercentage=70

四、监控与调优

通过jstat观察元空间使用情况:

jstat -gcmetacapacity <pid>

输出示例:

MCMN    MCMX     MC       CCSMN  CCSMX     CCSC    YGC   FGC  
0.0    107520.0  45632.0  0.0    1048576.0  4864.0  12    3

其中:

  • MC:当前Metaspace容量
  • MCMX:最大允许容量

五、终极解决方案

对于频繁爆Metaspace的应用,终极方案是:

  1. 排查类加载泄漏(比如未关闭的ClassLoader)
  2. 使用Java Flight Recorder监控:
jcmd <pid> JFR.start duration=60s filename=metaspace.jfr
  1. 分析生成的.jfr文件中的Class Load事件

六、总结

  • PermGen时代:手动设置大小,容易估算不足
  • Metaspace时代:虽然弹性更大,但仍需合理配置上限
  • 最佳实践
    • 开发环境设置较大阈值(如512MB)
    • 生产环境根据监控数据动态调整
    • 容器环境务必限制最大内存占比

记住:没有放之四海而皆准的配置,只有适合业务场景的调优!