元空间(Metaspace)

元空间是JVM用于存储类元数据(class metadata)的一块内存区域。

类元数据是Java类运行时表现形式,基本上包含Java类所需的全部信息。

java.lang.Class是生活在Java堆中的Java对象,但类元数据不是Java对象,也不生活在Java堆中。它们位于Java堆之外的本地内存区域中。该区域称为Metaspace

1.类元数据(class metadata)

它是Java在运行时已加载类的模型/模版,以便动态加载,链接,JIT编译和执行Java代码。

包含:

2.元空间何时被分配

  1. 元空间的分配和类加载相关。
  2. 类加载器加载类时将该类的元数据放入元空间内。

JVM-Metaspace - 图1

特别注意:不同的类加载器加载同一个类是产生的元数据是不同的。

3.元空间何时被回收/释放

类分配的元空间由其类加载器拥有。仅在卸载该类加载器时才释放它。

反过来说,只有在以下情况才会发生(请参阅:JLS 12.7。卸载类和接口)。

  • 此加载器加载的所有类都不再具有活动实例
  • 没有对这些类和它类加载器的引用
  • GC运行

JVM-Metaspace - 图2

而且,“释放元空间”并不一定意味着将内存返回给操作系统(OS)。

该内存的全部或一部分可以保留在JVM中;可以将其重新用于将来的类加载,但目前在JVM进程中仍未使用。

该部分的大小主要取决于元空间的碎片化程度—-元空间中已使用和未使用部分的紧密交错程度。同样,Metaspace(压缩类空间)的一部分将完全不返回给操作系统。

  • -XX:MaxMetaspaceSize确定允许元空间增长的最大提交大小。默认情况下是无限的。
  • -XX:CompressedClassSpaceSize确定元空间的一个重要部分(压缩类空间)虚拟大小。它的默认值为1G

4.元空间和GC

仅当GC确实运行并卸载类加载器时才释放元空间。因此,当在常规Java堆中GC收效甚微的情况下,运行GC清理陈旧的类元数据还是有意义的。在两种情况下会触发由元空间引起的GC:

  • 分配元空间时:JVM设置了一个阈值,如果不超过该阈值,它将不会通过触发GC而是先尝试收集旧的类加载器(从而重新使用其元空间)来增大元空间。那就是Metaspace GC阈值。它可以防止Metaspace陈旧元数据的增长。该阈值上下移动,根据元空间总大小而定。
  • 当遇到元空间OOM时(达到MaxMetaspaceSize上限时,或者当我们用尽压缩类(CompressedClassSpaceSize)空间时),JVM将尝试使用GC来纠正这种情况。如果它实际上卸载了类加载器,那么这样做就可以了,否则,即使我们有足够的Java堆,也可能会陷入一个糟糕的GC周期。

参考:

https://dzone.com/articles/java-class-metadata-a-user-guide

https://stuefe.de/posts/metaspace/what-is-metaspace/