jdk 1.7 之前 jvm 使用永久代 (PermGen) 来管理方法区, 而 jdk 1.8 之后 jvm 使用元数据区 (Metaspace) 来管理方法区, 实现了革命性得性能提升;
本文来简要梳理一下 PermGen 和 Metaspace 的区别和性能差异点;

Metaspace 与 PermGen 的对比

特性 PermGen (≤ Java 7) Metaspace (≥Java 8)
存储位置 堆内存的一部分 (固定大小) 本地内存
内存管理 由 JVM 管理,受 -XX:MaxPermSize 限制 由操作系统管理,默认无上限(受 -XX:MaxMetaspaceSize 约束)
static 变量处理 存储在 PermGen 中, 增加了方法区大小 存储在堆中, 彻底隔离方法区和运行时数据, 提升 Metaspace 的纯度
垃圾回收 由 Full GC 触发 由专门的 Metaspace GC (如 ClassUnloading) 回收
OOM 风险 会发生 OutOfMemoryError: PermGen space 可能发生 OutOfMemoryError: Metaspace

Metaspace 的关键参数:

1
2
3
-XX:MetaspaceSize=256M          # 初始大小
-XX:MaxMetaspaceSize=512M # 最大限制 (默认无限制)
-XX:+UseCompressedClassPointers # 压缩类指针(节省内存)

Metaspace 替代 PermGen 的原因

  1. 提升类卸载效率

    • PermGen 的类卸载只能依赖 Full GC, 回收不及时, 可能造成实质上的泄漏, 效率低;
    • Metaspace 由专门的 类卸载机制 管理, 可与 java 堆共享垃圾收集器, 当类加载器被回收时, 其加载的类元数据也会被自动回收, GC 更高效;
  2. 适应动态语言 (如 Groovy / Scala)

    • java 8 引入 Lambda 和方法引用, 需要更灵活的元数据存储;
  3. 无默认大小限制

    • PermGen 大小默认有限制而且限制很严格 (默认 64 MB ~ 82 MB)
      • 该阈值很低因为其占用了堆内存, 分配过大会影响其他对象在堆上的分配, 易导致 OutOfMemoryError (尤其在动态加载大量类时, 如 spring / cglib);
        • Metaspace 使用本地内存, 不占用宝贵的堆内存, 且分配效率高, 故允许默认无上限, 可按需分配 (当然生产环境建议依然要设置 MaxMetaspaceSize);

参考资料