Metaspace 详解
Metaspace 大家应该很熟悉了,所有线程共享的一块内存区域,主要存放已被虚拟机加载的类定义,方法定义,常量等一些元数据信息。
JDK1.8以后直接用本地内存实现方法区,并改名叫 Metaspace。正因为用本地内存(native memory),所以它的最大内存可以达到机器内存的极限,但关于它的调优参数一直有个误解。
MetaspaceSize 参数解析
-XX:MaxMetaspaceSize
:设置 Metaspace 的上限(默认无限制)-XX:MetaspaceSize
:初始高水位线-XX:MaxMetaspaceFreeRatio
:控制水位线下调-XX:MinMetaspaceFreeRatio
:控制水位线上调
-XX:MetaspaceSize
参数常被误解,官方文档明确说明:
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
. It is raised or lowered based on the options-XX:MaxMetaspaceFreeRatio
and-XX:MinMetaspaceFreeRatio
. If the committed space available for class metadata as a percentage of the total committed space for class metadata is greater than-XX:MaxMetaspaceFreeRatio
, then the high-water mark will be lowered. If it's less than-XX:MinMetaspaceFreeRatio
, then the high-water mark will be raised.当对应的 Java 类被卸载时,类元数据会被释放。Java 类会在垃圾回收时被卸载,垃圾回收可能会被触发以卸载类并释放类元数据。当为类元数据分配的空间达到一定水平(高水位标记)时,会触发垃圾回收。垃圾回收后,高水位标记可能会根据从类元数据中释放的空间大小而升高或降低。高水位标记会升高,以避免过早触发另一次垃圾回收。高水位标记最初设置为命令行选项 -XX:MetaspaceSize 的值。它会根据选项 -XX:MaxMetaspaceFreeRatio 和 -XX:MinMetaspaceFreeRatio 进行升高或降低。如果类元数据可用的已分配空间占类元数据总已分配空间的百分比大于 -XX:MaxMetaspaceFreeRatio,则高水位标记会降低。如果小于 -XX:MinMetaspaceFreeRatio,则高水位标记会升高。
大致意思就是当 MetaspaceSize 接近一个指定水位(high-water mark)的时候,会引发垃圾回收,这个初始化的水位值就是 -XX:MetaspaceSize
。
另一方面,水位值会上下浮动,上浮主要是为了避免过早引发一次垃圾回收,而上下浮动主要由 两个参数控制
-XX:MaxMetaspaceFreeRatio
:已提交的 Metaspace 空间中剩余可用的占全部提交空间的比例 大于 MaxMetaspaceFreeRatio ,说明 Metaspace 空间仍有富余,会降低水位。-XX:MinMetaspaceFreeRatio
:反之,如果比例小于 MinMetaspaceFreeRatio ,说明 Metaspace 空间比较紧张,会升高水位。这说明如果你只指定
-XX:MetaspaceSize
,然后再做 Metaspace oom实验,并不代表 gc 会在你指定的值发生,因为这个值会上下浮动。
最后再提下 -XX:MaxMetaspaceSize
就是字面意思,会给 MetaspaceSize 设一个上限,默认空间是无限的(default unlimited)。
-XX:MetaspaceSize
验证实验
前面说了,要做 Metaspace oom 实验还需要考虑 MaxMetaspaceFreeRatio 和 MinMetaspaceFreeRatio 的设置,这次我的 gc 参数设为如下,都设为 0 是避免水位上下浮动:
-XX:+UseG1GC -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:MetaspaceSize=8m -XX:MaxMetaspaceFreeRatio=0 -XX:MinMetaspaceFreeRatio=0
然后自定义一个大对象 OomObject :
public class OomObject {
String[] strings;
public OomObject() {
this(1);
}
/**
* size,unit mb
*/
public OomObject(int size) {
if (size <= 0) {
size = 1;
}
strings = new String[size];
for (int i = 0; i < size; i++) {
strings[i] = new String(createObject());
}
}
/**
* create 1mb object
*
* @return
*/
private byte[] createObject() {
return new byte[1024 * 300];
}
}
然后单元测试 会无限循环创建 OomObject 的子类(通过cglib,并且关闭缓存),这样会慢慢填充 MetaspaceSize,并且实时打印 MetaspaceSize log。
@Test
public void testMetaSpaceOom() throws InterruptedException {
// ArrayList<Object> objects = new ArrayList<Object>();
List<MemoryPoolMXBean> memoryPoolMXBeans = ManagementFactory.getMemoryPoolMXBeans();
while (true) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(OomObject.class);
enhancer.setUseCache(false);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy)
throws Throwable {
return proxy.invokeSuper(obj, args);
}
});
enhancer.create();
Thread.sleep(200);
for (MemoryPoolMXBean poolMXBean : memoryPoolMXBeans) {
if (MemoryType.NON_HEAP.equals(poolMXBean.getType())) {
MemoryUsage usage = poolMXBean.getUsage();
if (poolMXBean.getName().contains("Metaspace")) {
System.out.println(poolMXBean.getName() + ":" + usage.getCommitted());
}
}
}
}
}
最后我们可以看到成果,在 MetaspaceSize 达到 8m 的时候,会触发 Metadata GC Threshold gc,验证了 -XX:MetaspaceSize 的作用。
···
Metaspace:8388608
Metaspace:8388608
2021-08-26T15:59:08.109+0800: [GC pause (Metadata GC Threshold) (young) (initial-mark), 0.0046983 secs]
[Parallel Time: 3.6 ms, GC Workers: 8]
[GC Worker Start (ms): Min: 36019.2, Avg: 36019.4, Max: 36020.0, Diff: 0.8]
[Ext Root Scanning (ms): Min: 0.0, Avg: 0.5, Max: 0.8, Diff: 0.8, Sum: 4.0]
···