MetaspaceSize的误解

这几天生产上有台机器的Metaspace一直在告警,Metaspace使用达到了97%。使用-XX:MetaspaceSize=512m,告警也还在在持续,查看MC只有81536.0,显然这个参数没起作用。

也有人遇到类似的问题,并在openjdk上提过类似的bug,其实是一个注释的bug,最终在JDK-8151845中修复了。

Class metadata is deallocated when the corresponding Java class is unloaded. Java classes are unloaded as a result of garbage collection, and garbage collections may be induced in order to unload classes and deallocate class metadata. When the space committed for class metadata reaches a certain level (a high-water mark), a garbage collection is induced. After the garbage collection, the high-water mark may be raised or lowered depending on the amount of space freed from class metadata. The high-water mark would be raised so as not to induce another garbage collection too soon. The high-water mark is initially set to the value of the command-line option MetaspaceSize. It is raised or lowered based on the options MaxMetaspaceFreeRatio and MinMetaspaceFreeRatio. If the committed space available for class metadata as a percentage of the total committed space for class metadata is greater than MaxMetaspaceFreeRatio, then the high-water mark will be lowered. If it is less than MinMetaspaceFreeRatio, then the high-water mark will be raised.

查看了Oracle的手册,Metaspace的GC会在committed size达到high-water mark之后发生。并且GC之后high-water mark会变化:变大或者变小,变大的话会防止下次GC发生得太早。high-water mark的默认初始大小20.8M,通过MetaspaceSize来设置,可见MetaspaceSize是控制Metaspace发生GC的阈值。GC后high-water mark的变化,通过MaxMetaspaceFreeRatio和MinMetaspaceFreeRatio控制。

"Class metadata is deallocated when the corresponding Java class is unloaded. Java classes are unloaded as a result of garbage collection, and garbage collections may be induced to unload classes and deallocate class metadata. When the space committed for class metadata reaches a certain level (a high-water mark), a garbage collection is induced. After the garbage collection, the high-water mark may be raised or lowered depending on the amount of space freed from class metadata. The high-water mark would be raised so as not to induce another garbage collection too soon. The high-water mark is initially set to the value of the command-line option -XX:MetaspaceSize. [...] The default size of -XX:MetaspaceSize is platform-dependent and ranges from 12 MB to about 20 MB."

"类元数据在卸载相应的Java类时被解除分配。由于垃圾回收,Java类被卸载,并且可以诱导垃圾回收来卸载类并解除分配类元数据。当为类元数据提交的空间达到某个级别 (高水位线) 时,将诱导垃圾收集。在垃圾回收之后,根据从类元数据中释放的空间量,可以提高或降低高水位线。高水位线将被提高,以免过早引发另一次垃圾收集。高水位标记最初设置为命令行选项-XX:MetaspaceSize的值。[...]-XX:MetaspaceSize的默认大小取决于平台,范围从12 MB到大约20 MB。”

MaxMetaspaceSize默认为-1,无限大。不过如果没有限制的话,一直增大会被系统干掉进程。最好还是设置一下,比如1G。

下面是我测试了分别设置MetaspaceSize、MaxMetaspaceSize、InitialBootClassLoaderMetaspaceSize为1G,Metaspace的变化。

-XX:MetaspaceSize=1024m

committed: 29360128 init: 0 max: -1 used: 28440648

-XX:MaxMetaspaceSize=1024m

committed: 29360128 init: 0 max: 1073741824 used: 28503552

-XX:InitialBootClassLoaderMetaspaceSize=1024m

committed: 1087635456 init: 0 max: -1 used: 28500344

三个参数都是没有改变init的大小,但是InitialBootClassLoaderMetaspaceSize改变了committed的大小,其实也是最终我们要的设置。

关于这个参数,可以看你假笨的关于Metaspace的源码解读,发现的有点晚了。

最后的解决方案是使用这个配置:-XX:MaxMetaspaceSize=1024m -XX:InitialBootClassLoaderMetaspaceSize=256m

推荐阅读更多精彩内容