Skip to content

更正:MaxMetaspaceSize如果不指定大小的话,不会耗尽内存 #1204

Closed

Description

@charlienss

问题截图:

image

关于元空间:

使用Java 8以后,关于元空间的JVM参数有两个:-XX:MetaspaceSize=N和 -XX:MaxMetaspaceSize=N,对于64位JVM来说,元空间的默认初始大小是20.75MB,默认的元空间的最大值是无限。MaxMetaspaceSize用于设置metaspace区域的最大值,这个值可以通过mxbean中的MemoryPoolBean获取到,如果这个参数没有设置,那么就是通过mxbean拿到的最大值是-1,表示无穷大。

由于调整元空间的大小需要Full GC,这是非常昂贵的操作,如果应用在启动的时候发生大量Full GC,通常都是由于永久代或元空间发生了大小调整,基于这种情况,一般建议在JVM参数中将MetaspaceSize和MaxMetaspaceSize设置成一样的值,并设置得比初始值要大。

源码分析

MetaspaceSize表示metaspace首次使用不够而触发Full GC的阈值,只对触发起作用,原因是:垃圾搜集器内部是根据变量 _capacity_until_GC来判断metaspace区域是否达到阈值的,初始化代码如下所示:

void MetaspaceGC::initialize() {	
  // Set the high-water mark to MaxMetapaceSize during VM initializaton since	
  // we can't do a GC during initialization.	
  _capacity_until_GC = MaxMetaspaceSize;	
}

GC收集器会在发生对metaspace的回收会,会计算新的capacityuntil_GC值,以后发生FGC就跟MetaspaceSize没有关系了。

关于MetaspaceSize默认值

MetaspaceSize的默认大小取决于平台,范围从12 MB到大约20 MB,具体参考官方说明文档。

官方文档地址:

https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/considerations.html

image

那么默认不设置的时候就如下所示:
-XX:MetaspaceSize=12M/20M
-XX:MaxMetaspaceSize=N

那么问题来了,此时MaxMetaspaceSize会耗尽内存吗?

继续查看那篇官方文,具体查看截图所示:

image

翻译后如下:

卸载相应的Java类时,将重新分配类元数据。 Java类是作为垃圾回收的结果而卸载的,并且可以引发垃圾回收以便卸载类和取消分配类元数据。当用于类元数据的空间达到一定级别(高水位线)时,将引发垃圾回收。垃圾收集之后,高水位线可能会升高或降低,具体取决于类元数据释放的空间量。高水位线将被抬高,以免过早引起另一次垃圾收集。高水位线最初设置为命令行选项MetaspaceSize的值。根据选项MaxMetaspaceFreeRatio和MinMetaspaceFreeRatio来升高或降低它。如果可用于类元数据的承诺空间占类元数据的总承诺空间的百分比大于MaxMetaspaceFreeRatio,则高水位线将降低。如果小于MinMetaspaceFreeRatio,则高水位线将升高。

注意MaxMetaspaceFreeRatio

将元空间限制从其有效的无限默认值减少将具有完全不同的目的:避免无限的元空间增长。

问题是那只是一个上限。实际提交的(即_当前元_空间的大小)将较小。实际上,有一个名为MaxMetaspaceFreeRatio(默认为70%)的设置,这意味着实际的元空间大小永远不会超过其占用率的230%。

而要使其增长,首先必须将其填满,强制进行垃圾回收(元空间已满),以尝试释放对象,并且只有当它不能满足其MinMetaspaceFreeRatio(默认40%)目标时,才将_当前元_ 空间扩展到不超过GC周期后占用率达到230%。

因此,在实践中,除非应用程序不断泄漏类装入器/类或生成大量动态代码,否则实际的元空间大小应稳定在与其实际需要接近的范围内。

JDK默认有设置MaxMetaspaceSize的值,和服务器配置有关

不过就算有以上的要求,以上还是有风险导致本地内存无限增长,但是使用如下命令查看本机参数后:

java -XX:+PrintFlagsInitial

会发现防止因为某些情况导致Metaspace无限的使用本地内存,影响到其他程序。在本机上该MaxMetaspaceSize参数有默认值,本人window上默认值为4294967295B(大约4096MB),在Linux下为同样可以查询其默认值。

image

结论:

MaxMetaspaceSize如果不指定大小的话,不会耗尽内存,原因如下:

  1. MetaspaceSize的扩容会Full GC, 回收后程序有更多可用空间,可能之后空间够用不会再触发扩容。
  2. MaxMetaspaceFreeRatio控制高水位,要使其增长,首先必须将其填满,强制进行垃圾回收(元空间已满),以尝试释放对象,并且只有当它不能满足其MinMetaspaceFreeRatio(默认40%)目标时,才将当前元 空间扩展到不超过GC周期后占用率达到230%,垃圾收集之后,高水位线可能会升高或降低。
  3. -XX:MaxMetaspaceSize=N 这个参数用于限制Metaspace增长的上限,防止因为某些情况导致Metaspace无限的使用本地内存,影响到其他程序。在本机上该参数的默认值为4294967295B(大约4096MB)。

Activity

  1. llxLLY commented on May 27, 2021

    @llxLLY

    这个“耗尽内存”中的内存语义不明确,不过有一些Full GC的情况确实是MetaspaceSize太小了。

  2. Repository owner deleted a comment from 78736013 on Mar 12, 2023
  3. Repository owner deleted a comment from 78736013 on Mar 12, 2023
  4. ZhenghaeHo commented on Mar 30, 2023

    @ZhenghaeHo
    Contributor

    问题截图:

    image

    关于元空间:

    使用Java 8以后,关于元空间的JVM参数有两个:-XX:MetaspaceSize=N和 -XX:MaxMetaspaceSize=N,对于64位JVM来说,元空间的默认初始大小是20.75MB,默认的元空间的最大值是无限。MaxMetaspaceSize用于设置metaspace区域的最大值,这个值可以通过mxbean中的MemoryPoolBean获取到,如果这个参数没有设置,那么就是通过mxbean拿到的最大值是-1,表示无穷大。

    由于调整元空间的大小需要Full GC,这是非常昂贵的操作,如果应用在启动的时候发生大量Full GC,通常都是由于永久代或元空间发生了大小调整,基于这种情况,一般建议在JVM参数中将MetaspaceSize和MaxMetaspaceSize设置成一样的值,并设置得比初始值要大。

    源码分析

    MetaspaceSize表示metaspace首次使用不够而触发Full GC的阈值,只对触发起作用,原因是:垃圾搜集器内部是根据变量 _capacity_until_GC来判断metaspace区域是否达到阈值的,初始化代码如下所示:

    void MetaspaceGC::initialize() {	
      // Set the high-water mark to MaxMetapaceSize during VM initializaton since	
      // we can't do a GC during initialization.	
      _capacity_until_GC = MaxMetaspaceSize;	
    }

    GC收集器会在发生对metaspace的回收会,会计算新的capacityuntil_GC值,以后发生FGC就跟MetaspaceSize没有关系了。

    关于MetaspaceSize默认值

    MetaspaceSize的默认大小取决于平台,范围从12 MB到大约20 MB,具体参考官方说明文档。

    官方文档地址:

    https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/considerations.html

    image

    那么默认不设置的时候就如下所示:
    -XX:MetaspaceSize=12M/20M
    -XX:MaxMetaspaceSize=N

    那么问题来了,此时MaxMetaspaceSize会耗尽内存吗?

    继续查看那篇官方文,具体查看截图所示:

    image

    翻译后如下:

    卸载相应的Java类时,将重新分配类元数据。 Java类是作为垃圾回收的结果而卸载的,并且可以引发垃圾回收以便卸载类和取消分配类元数据。当用于类元数据的空间达到一定级别(高水位线)时,将引发垃圾回收。垃圾收集之后,高水位线可能会升高或降低,具体取决于类元数据释放的空间量。高水位线将被抬高,以免过早引起另一次垃圾收集。高水位线最初设置为命令行选项MetaspaceSize的值。根据选项MaxMetaspaceFreeRatio和MinMetaspaceFreeRatio来升高或降低它。如果可用于类元数据的承诺空间占类元数据的总承诺空间的百分比大于MaxMetaspaceFreeRatio,则高水位线将降低。如果小于MinMetaspaceFreeRatio,则高水位线将升高。

    注意MaxMetaspaceFreeRatio

    将元空间限制从其有效的无限默认值减少将具有完全不同的目的:避免无限的元空间增长。

    问题是那只是一个上限。实际提交的(即_当前元_空间的大小)将较小。实际上,有一个名为MaxMetaspaceFreeRatio(默认为70%)的设置,这意味着实际的元空间大小永远不会超过其占用率的230%。

    而要使其增长,首先必须将其填满,强制进行垃圾回收(元空间已满),以尝试释放对象,并且只有当它不能满足其MinMetaspaceFreeRatio(默认40%)目标时,才将_当前元_ 空间扩展到不超过GC周期后占用率达到230%。

    因此,在实践中,除非应用程序不断泄漏类装入器/类或生成大量动态代码,否则实际的元空间大小应稳定在与其实际需要接近的范围内。

    JDK默认有设置MaxMetaspaceSize的值,和服务器配置有关

    不过就算有以上的要求,以上还是有风险导致本地内存无限增长,但是使用如下命令查看本机参数后:

    java -XX:+PrintFlagsInitial

    会发现防止因为某些情况导致Metaspace无限的使用本地内存,影响到其他程序。在本机上该MaxMetaspaceSize参数有默认值,本人window上默认值为4294967295B(大约4096MB),在Linux下为同样可以查询其默认值。

    image

    结论:

    MaxMetaspaceSize如果不指定大小的话,不会耗尽内存,原因如下:

    1. MetaspaceSize的扩容会Full GC, 回收后程序有更多可用空间,可能之后空间够用不会再触发扩容。
    2. MaxMetaspaceFreeRatio控制高水位,要使其增长,首先必须将其填满,强制进行垃圾回收(元空间已满),以尝试释放对象,并且只有当它不能满足其MinMetaspaceFreeRatio(默认40%)目标时,才将当前元 空间扩展到不超过GC周期后占用率达到230%,垃圾收集之后,高水位线可能会升高或降低。
    3. -XX:MaxMetaspaceSize=N 这个参数用于限制Metaspace增长的上限,防止因为某些情况导致Metaspace无限的使用本地内存,影响到其他程序。在本机上该参数的默认值为4294967295B(大约4096MB)。

    不知道你是不是用机器翻译过来的,很多句子不通顺

  5. Snailclimb commented on Mar 30, 2023

    @Snailclimb
    Owner

    @ZhenghaeHo 部分应该是直接翻译的。

  6. HydroCarbon commented on Jun 26, 2023

    @HydroCarbon
    image 在 macOS 上的截图,机器为 64 位,这个值相于就是无穷大了,可能你的机器是 32 位或者你安装的 JVM 是 32 位的?
  7. Snailclimb commented on Aug 24, 2026

    @Snailclimb
    Owner

    感谢反馈!这个问题已经在当前文档中修复:docs/java/jvm/jvm-parameters-intro.md 已更新:不显式设置 MaxMetaspaceSize 时元空间无固定上限,持续增长会消耗本地内存。 如果觉得仍有问题,欢迎重新打开或新开 issue 说明。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions