MaxMetaspaceSize和CompressedClassSpaceSize是控制元空间大小的旋钮
现在,这些参数可能有点混乱。首先,它们有两种,它们有着微妙的不同含义,它们相互影响。
所以让我们仔细看看。我们将详细解释这些参数是如何工作的。然后,我们将分析单个类平均需要多少元空间。最后,我们将尝试导出一些粗略的经验法则,并检查默认行为是什么。
推荐阅读:https://javakk.com/405.html
这里再明确两个概念:
- MaxMetaspaceSize:这是对提交的元空间最大值的“软”限制。它包括非类空间和类空间。从这个意义上说,它是“软”的,因为除了希望有一个限制之外,没有紧迫的技术原因。它是完全可选的,实际上在默认情况下是关闭的。
- CompressedClassSpaceSize:这是一个硬限制,定义压缩类空间的虚拟大小。“很难”,因为我们需要在VM启动时修复它,而且永远无法更改它。如果我们省略它,它默认为1G。
此图说明了这些限制是如何工作的:
红色部分是Metaspace的提交部分的总和,包括非类空间节点和一个巨大的类空间节点。此总和受-XX:MaxMetaspaceSize限制。尝试提交超过-XX:MaxMetaspaceSize的内存将导致OutOfMemoryError("Metaspace")。
-XX:MaxMetaspaceSize的目的当然很简单:对提交的Metaspace大小有一个最大限制。它永远不会超过这一点。
另一方面,确定了一个保留空间的大小。它包括已提交部分和(蓝色)尚未提交部分。如果该节点已满,我们将得到一个OutOfMemoryError("Compressed Class Space")。
注意:如第3部分所述,我们使用压缩类指针开关。默认情况下,它是打开的,但如果关闭,则不会有压缩的类空间,-XX:CompressedClassSpaceSize将被忽略,并且元空间仅受-XX:MaxMetaspaceSize的限制。整个事情变得简单得多。
那么,这是什么意思?
加载Java类时,它需要非类空间和类空间中的内存。由于后者始终是有限的,因此即使-XX:MaxMetaspaceSize不受限制,我们也会有效地为元空间的增长设置一个上限。
这个cap命中的位置取决于加载的类的大小(详见下文),这决定了非类空间和类空间使用率之间的比率。
例如,假设(合理)比率为1:5类:非类空间每个calss的大小。
这意味着对于默认设置-XX:CompressedClassSpaceSize为1G,我们的上限是~6G:将1G默认大小的压缩类空间填充到边缘将导致分配~5G的非类空间。
一个类需要多少元空间?
对于每个加载的类,将从类和非类空间的类元数据中分配空间。现在去哪?
进入calss类空间
calss空间采用固定尺寸的Klass结构。
接下来是两个可变大小的结构,vtable和itable。前者的大小随方法的数量而增加,后者的大小随从已实现接口继承的接口方法的数量而增加。
接下来是一个映射,它描述对象引用成员在Java类中的位置,即非静态Oopmap。这种结构的尺寸也是可变的,但通常非常小。
vtable和itable通常都很小,但是对于奇怪的大型类,它们可以增长到巨大的比例。一个包含30000个方法的类的vtable的大小将是240k,当从一个包含30000个方法的接口派生时也是一个itable。但这些都是测试用例,除了自动代码生成之外,我们在野外找不到这样的类。
进入非calss空间
在非类空间中有很多东西,其中最大的贡献者是:
- 常量池,大小可变。
- 任何一个类方法的元数据:ConstMethod结构,包含大量相关的、大小可变的嵌入式结构,如方法字节码、局部变量表、异常表、参数信息、签名等。
- 用于控制JIT的运行时方法数据
- 注释
注意:存储在Metaspace中的结构都是从MetaspaceObj派生的,所以下面的类型树将给您一个很好的印象,什么是在Metaspace中。
类与非类空间的大小比
让我们看看对于典型应用程序,类空间和非类空间的比率是多少。
这里有一个WildFly1服务器,独立的,16.0.0,运行在sapmache11上。没有加载应用程序,只有启动后的裸机服务器。我们检查我们需要多少元空间,以及每个类平均需要多少空间。我们用jcmd测量虚拟机元空间.
loader | #classes | non-class space (avg per class) | class space (/avg per class) | ratio non-class/class |
---|---|---|---|---|
all | 11503 | 60381k (5.25k) | 9957k (.86k) | 6.0 : 1 |
bootstrap | 2819 | 16720k (5.93k) | 1768k (0.62k) | 9.5 : 1 |
app | 185 | 1320k (7.13k) | 136k (0.74k) | 9.7 : 1 |
anonymous | 869 | 1013k (1.16k) | 475k (0.55k) | 2.1 : 1 |
这说明了什么?
对于标准类(假设bootstrap和app loader加载的类被视为标准类),平均每个类的非类空间约为5-7k,类空间为600-900字节。
匿名(lambda)类要小得多,这并不奇怪,但有趣的是,类和非类空间的使用比例也被扭曲了:相对于非类空间,我们需要更多的类空间。这并不奇怪,因为Lambda类很小,但是Klass结构的开销不能缩小到sizeof(Klass)结构本身之下。所以,我们来到1k的非类空间,.5k类空间。
注意,在我们的例子中,匿名类并不重要。为了使它们的影响显著,必须分配更多的匿名类。
元空间metaspace默认大小
如果不对元空间设置任何限制,它能容纳多少个类?
默认情况下,MaxMetaspaceSize是无限的,而CompressedClassSpaceSize的大小是1G。这意味着我们遇到的唯一限制是CompressedClassSpaceSize。
使用上面的示例数字(~5-7k非类空间,600-900字节的类空间),在理想情况下(如果没有碎片化,没有浪费),在压缩类空间中遇到OutOfMemoryError之前,我们可以容纳大约1..1,5百万个类。这是一个非常多的元数据,而且肯定有点过头了。
但是,CompressedClassSpaceSize只是保留空间,而不是提交的。所以它“没有那么大的伤害”—我们实际上只使用类空间的提交部分。
元空间metaspace限制
免责声明:不要盲目使用你在网上发现的任何规则,尤其是不在生产中!
请注意,这里实际上没有那么多选择。当然可以限制元空间的增长。但是如果你这样做了,并且你的程序需要比你允许的更多的空间来存放类元数据,它只会因为OOM而失败。在重写程序以加载较少的类之前,您无能为力。
与Java堆比较:您可以在一定的限制内增加或减少Java堆,而不会影响程序的主要功能—这里有某种灵活性,这是元空间所没有的。
那么你为什么要限制元空间呢?
- 作为一个警告系统,一个“健全的封套”,知道什么时候元空间的消耗超过了它应该有人看看。
- 有时可能需要限制虚拟内存大小(又称ps“size”列)。虽然常驻集大小(resident set size,RSS)通常是真正有趣的统计值,但虚拟内存大小可能会导致VM进程达到配额。
注意:JDK版本依赖性:jdk8中的元空间比jdk11或更高版本更容易受到碎片的影响。请记住这一点,如果您运行的是较旧的jdk,那么可以增加一个健康的裕度。
如果您想限制元空间大小,使其具有“健全的封套”,并且不关心虚拟进程的大小,那么最好只设置MaxMetaspaceSize并保持CompressedClassSpaceSize不变。如果单独使用,CompressedClassSpaceSize将默认为MaxMetaspaceSize的80%—这可能会有点过度补偿,但请记住,这只是保留空间,而不是提交。
除了MaxMetaspaceSize之外,减少CompressedClassSpaceSize的唯一原因是减少VM进程的虚拟内存大小。但请记住,如果将CompressedClassSpaceSize设置得太低,则可能会在用完MaxMetaspaceSize之前填充压缩的类空间。也就是说,在大多数情况下,1:2(CompressedClassSpaceSize=MaxMetaspaceSize/2)的比率应该是安全的。
那么您应该将MaxMetaspaceSize设置为多大?首先计算平均预期的元空间使用量。作为第一个指导原则,您可以使用上面给出的数字,每个类有大约1K个类空间,每个class3个大约8K个非类空间,并将这些数字与您期望加载的类的数量相乘。
例如,如果您的应用程序计划加载10000个类,理论上您只需要10M的类空间,80M的非类空间。
现在你需要考虑一个安全裕度。因子2是大多数情况下的安全极限。当然,您可以降低级别并尝试运气,但要准备好通过更改代码或增加MaxMetaspaceSize来处理元空间OOM。
因子2将使我们拥有20M的类空间,160M的非类空间,总的元空间大小为180M。
除特别注明外,本站所有文章均为老K的Java博客原创,转载请注明出处来自https://javakk.com/413.html
暂无评论