了解元空间和类空间 GC 日志条目

了解元空间和类空间 GC 日志条目

原连接:https://poonamparhar.github.io/understanding-metaspace-gc-logs/
了解元空间和类空间 GC 日志条目
2021 年 11 月 30 日
在这篇文章中,我想分享一些关于 Metaspace 和 Compressed class space 的细节,以及如何阅读和解释相关的 GC 日志信息。

元空间
元空间是存储类元数据的本机内存区域。当一个类被 JVM 加载时,它的元数据(即它在 JVM 中的运行时表示)被分配到元空间中。

随着越来越多的类被加载,元空间占用率会增加。并且,当类加载器及其所有加载的类在 Java 堆中变得不可访问时,元空间中的关联类元数据将有资格进行释放。类元数据通过垃圾回收周期进行清理。

现在,您可能会问,在运行 GC 以清理不需要的元数据之前,Metaspace 占用率可以增长多少有限制吗?就在这里。JVM 在内部为元空间占用率维护一个阈值(又名高水位线),并在分配到元空间时检查该阈值。并且,当在该阈值内无法满足特定分配时,将调用“元数据 GC 阈值”垃圾回收周期,如以下日志记录所示。

在这里插入图片描述

**请注意,此博客中的示例日志是使用 Java 8 生成的。

有两个 JVM 选项 - MetaspaceSize和MaxMetaspaceSize可用于控制Metaspace的初始大小和最大大小。如果命令行上未明确提供这些值,则可以使用JVM 选项-XX:+PrintFlagsFinal查看 JVM 设置的值。
在这里插入图片描述

正如我们所见,MaxMetaspaceSize,如果在命令行上未配置,则几乎是无限的。MetaspaceSize设置Metaspace的初始容量和阈值,在该阈值处调用 GC 清理空间,或在无法回收足够空间时扩展容量。

元空间使用信息
让我们看一些使用 Java 程序生成的日志条目,该程序将类加载和卸载到元空间中。我用于执行程序的 JVM 选项是:

-XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -XX:-UseCompressedClassPointers

暂时忽略UseCompressedClassPointers选项;我们将在下一节中讨论这一点。

在运行时,Java 程序会遇到几个“元数据 GC 阈值”GC。让我们仔细检查一个这样的日志记录。
在这里插入图片描述

在这里,我们可以看到在这次特定的 Full GC 之前,Metaspace 占用率为 410659K,而在 GC 之后,占用率没有变化。括号中的数字表示新保留的元空间大小,即 456704K。

由于我们添加了-XX:+PrintHeapAtGC选项,GC 之前和之后的内存使用信息也会被打印出来,并帮助我们了解为什么会调用此 GC 以及元空间大小如何随此特定集合而变化。

在这里插入图片描述

从上面的 Metaspace 细节来看,在 GC 之前,JVM 已经预留了 456704K 并已提交 455424K 用于元数据分配。元空间使用量为 410659K。这里要注意的一件有趣的事情是容量,它充当 Metaspace 的内部高水位线。当在该阈值内无法满足特定元数据分配请求时,将调用 GC 以清理并在元空间中腾出一些空间。而且,如果该 GC 无法提供足够的可用空间,则会提交更多内存并提高容量。在本例中,此阈值设置为 455416K,其中 410659K 已在使用中。在导致此 Full GC 发生的容量限制内无法满足新的元数据分配。

GC后调整Metaspace的reserved、committed、容量边界,以满足元数据空间需求。我们可以在下一次 GC 事件之前记录的“Before GC”详细信息中找到新值。
在这里插入图片描述

上述Metaspace日志条目显示,在GC调用#16之后,保留空间和提交空间分别增加到759808K和759040K。高水位线也提升到了759024K,使用量从上次GC后的410659K增加到了685064K。

压缩的类空间
我们也可以有一个单独的空间作为元空间的一部分来存储元数据的类部分。这个单独的空间称为Compressed class space,其中的类部分元数据是使用 Java 对象的 32 位偏移量引用的。

在上一节中,为了简化日志,我使用选项-XX:-UseCompressedClassPointers禁用了单独的压缩类空间的使用。在 64 位平台上,压缩类空间默认启用,默认保留空间大小为 1 GB。Compressed 类空间的保留空间是在JVM 初始化时设置的,其大小以后不能更改。Hotspot JVM 允许压缩类空间的最大保留空间大小为 3GB,可以使用 JVM 选项-XX:CompressedClassSpaceSize=n进行配置。

“压缩类空间”使用信息
现在,让我们看一下使用相同程序生成的一些日志记录,但启用了单独的压缩类空间作为元空间的一部分。

在这里插入图片描述

上面的前两个日志条目显示总元空间使用量为 20669K,其中 1243K 被类空间使用。类似地,总元空间容量 (HWM) 为 25846K,其中此 GC 调用的类空间容量 (HWM) 为 2086K。重申一下,容量是一个阈值,当使用量接近容量时,就会调用 GC。

在这种情况下,Metaspace 提交的大小为 35456K,并且从该空间中,为类空间提交了 8064K。这意味着 35456K-8064K=27392K 是元数据的非类部分的已提交空间。JVM 选项MaxMetaspaceSize可用于设置总提交大小的最大限制。
在这里插入图片描述

至于预留空间,我们可以看到类空间有1GB(1048576K)的虚拟预留空间(默认),总的Metaspace(包括类空间)有1077248K的预留空间。

java.lang.OutOfMemoryError
了解我们应用程序的类和非类元数据的空间需求并相应地调整元空间的大小很重要。该MaxMetaspaceSize JVM选项设置在元空间的COMMITED大小的上限,如果没有配置足够大,可能会导致java.lang.OutOfMemoryError:元空间。此外,如前所述,为 Compressed 类空间配置的所有空间都是预先保留的,并且不能在运行时以后增长。所以,如果为Compressed class space预留的空间没有配置的足够大,我们可能会遇到java.lang.OutOfMemoryError: Compressed class space failure。

碎片化
请注意,由于元空间中的碎片(在元空间的非类部分和类部分),我们也可能会遇到 OutOfMemoryError 失败。让我们看几个表明元空间碎片化的例子。

在这里插入图片描述

上面的日志记录显示 Java 程序因java.lang.OutOfMemoryError: Metaspace错误而失败。从Metaspace和class space使用明细可以看出,Metaspace的non-class-part的commited size和容量分别是(2097132K-168960K=)1928172K和(2097086K-168958K=)1928128K,而只有1657被非类元数据使用。因此,Metaspace 的非类部分显然有足够的可用空间,但应用程序仍然失败并出现 OutOfMemoryError,这表明 Metaspace 中存在碎片。

同样,下面的日志显示类空间使用量为24868K,而提交的类空间大小为1048576K,表明类空间有足够的可用空间,但仍然分配到类空间失败,出现OutOfMemoryError。

在这里插入图片描述

增强JDK-8198423解决了 Metaspace 中的碎片问题。它被集成到 Java 11 中。由于 Metaspace 禁止单独的类空间是无限的,为了避免在 Java 8 中运行时由于碎片而导致的 OutOfMemoryError 失败,在没有 Compressed 类空间的情况下运行可能会有所帮助。请注意,此配置将导致 Java 堆空间需求增加,因为没有压缩类空间 Java 对象使用 64 位 klass 指针而不是 32 位偏移量引用类元数据。

  • 23
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值