Metaspace
Metaspace 大家应该很熟悉了,所有线程共享的一块内存区域,主要存放已被虚拟机加载的类定义,方法定义,常量等一些元数据信息,运行时常量池(Runtime Constant Pool)也是方法区的一部分,Class 文件中的常量池表(Constant Pool Table,里面各种字面量和符号引用),被类加载后就放入方法区的运行时常量池 。运行时常量池并不只有编译时才往里放东西,运行时也可以放新的常量,比如 String.intern()。有一个别名叫“非堆”。
jdk1.8以后直接用本地内存实现方法区,并改名叫 Metaspace。正因为用本地内存(native memory),所以它的最大内存可以达到机器内存的极限,但关于它的调优参数一直有个误解。
MetaspaceSize
-XX:MetaspaceSize
并不代表初始的 Metaspace 大小,在 oracle doc 中,有明确的解释
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.
大致意思就是当 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]
···