What is Metaspace? - tenji/ks GitHub Wiki

What is Metaspace?

OpenJDK 使用 Metaspace 来存储它的类元数据。它占用了 Java VM 进程的非 Java 堆内存的很大一部分。

我们解释了它是什么以及我们为什么需要它。简短、快速且希望轻松阅读,无需深入研究 VM 内部。应该是每个人都能消化的。

Note: JDK version dependencies: Metaspace implementation changed quite a bit since JDK 8. In these articles, when not explicitly stated differently, we talk about JDK 11.

Metaspace 是 VM 用来存储类元数据的内存。

类元数据是 JVM 进程中 Java 类的运行时表示——基本上是 JVM 与 Java 类一起工作所需的任何信息。这包括但不限于来自 JVM 类文件格式的数据的运行时表示。

比如:

  • “Klass” 结构 - Java 类运行时状态的 VM 内部表示。这包括 vtable 和 itable。
  • 方法元数据 - 运行时等效于类文件中的 method_info,包含 bytecode, exception table, constants 等内容。
  • The constant pool
  • 注解
  • 在运行时收集的方法计数器作为 JIT 决策的基础
  • 其它

Note: We will examine all this in more detail in Part 4 of this series.

Another good introduction into class metadata in hotspot is Andrew Dinn’s talk at FOSDEM 18.

尽管 java.lang.Class 是一个存在于 Java 堆中的 Java 对象,但类元数据本身并不是 Java 对象,也不存在于 Java 堆中。它们位于 Java 堆之外的本机内存区域。该区域称为 Metaspace

什么时候分配 Metaspace?

Metaspace 的分配与 class loading 耦合。

当一个类被加载并准备好它在 JVM 中的运行时表示时,Metaspace 由它的类加载器分配来存储类的元数据。

什么时候回收 Metaspace?

为类分配的 Metaspace 归其类加载器所有。它仅在该类加载器本身被卸载时才被释放,而不是之前。

只有在此加载器加载的所有类不再有活动实例,并且没有对这些类及其类加载器的引用,并且 GC 确实运行后,才会发生 Metaspace 被回收这种情况(see: JLS 12.7. Unloading of Classes and Interfaces

Memory is often retained!

但是,“释放元空间”并不一定意味着将内存返还给操作系统。

该内存的全部或部分可能会保留在 JVM 中;它可能会在未来的类加载中重用,但目前它在 JVM 进程中仍未使用。

该部分有多大主要取决于元空间的碎片化程度 - 元空间的已用部分和空闲部分的交错程度如何。此外,Metaspace(Compressed Class Space)的一部分根本不会返回给操作系统。

Note: An in-depth look at the release logic follows in Part 2.

Note: Concern about Metaspace footprint is a good reason to upgrade to JDK 11 - it got a number of significant improvements in this area, reducing fragmentation and memory waste.

MaxMetaspaceSize, CompressedClassSpaceSize

有两个参数可以限制 Metaspace 大小:

  • -XX:MaxMetaspaceSize确定允许 Metaspace 增长的最大 committed 大小。默认情况下是无限的。
  • -XX:CompressedClassSpaceSize决定了 Metaspace 的一个重要部分,压缩类空间的虚拟大小。它的默认值为 1G(注意:reserved 空间,不是 committed)。

我们将更详细地讨论这一点,请参考:Sizing Metaspace

Metaspace and GC

仅当 GC 确实运行并卸载类加载器时才会释放 Metaspace。因此,在某些时候运行 GC 来清理过时的类元数据是有意义的,即使在常规 Java 堆中 GC 没有获得太多收益。包含 Metaspace 的 GC 在两种情况下被触发:

  • When allocating Metaspace:VM 持有一个阈值,超过该阈值之后,它会首先通过 GC 尝试收集旧的类加载器以便重用它们的元空间,如果空间还是不足,才会增长元空间。这个概念是 Metaspace GC 阈值。它可以防止 Metaspace 在不释放陈旧元数据的情况下增长。该阈值上下移动,大致跟随 committed metaspace 的总和一定的余量。
  • When encountering a Metaspace OOM - 当 committed memory 总和达到 MaxMetaspaceSize 上限或当我们用完 Compressed Class Space 时会发生这种情况 - GC 试图补救这种情况。如果它真的卸载了类加载器,这很好,否则即使我们有足够的 Java 堆,我们也可能会遇到糟糕的 GC 循环。

参考链接