4.1 java.lang.OutOfMemoryError: Metaspace 概述
Java应用只允许使用有限的内存. 你的应用可以用的准确的内存大小在启动的时候指定. 展开来说, Java内存被分成不同的区域, 具体如下图:
所有的这些区域, 包括元空间(metaspace)区域, 可以在JVM启动的时候指定. 如果你没有指定这些的大小, 平台相关的默认配置会被应用.
java.lang.OutOfMemoryError: Metaspace
消息表明Metaspace区内存耗尽.
4.2 原因
如果你不是Java领域的新手, 你可能会熟悉Java内存管理的另一个叫做: PermGen的概念. 从Java 8开始, Java的内存模型发生明显改变. 引入一个叫做Metaspace的新的内存区域, Permgen被移除. 这个变更是基于多种原因的, 包括但不限于:
- PermGen需要的内存大小难以预测. 它导致有可能由于内存不足触发
java.lang.OutOfMemoryError: Permgen
错误或预留过多导致浪费资源. - GC性能的提升, 在没有GC暂停和元数据的特定迭代器的情况下启用并发类数据分配.
- 支持进一步优化,如G1并发类卸载。
所以, 如果你熟悉PermGen, 那么你需要知道的就是 – Java 8之前版本在PermGen里存在的一切东西(组成类的类的名字和字段, 方法的字节码, 常量池信息, 对象数组和类型数组, 以及实时编译优化) – 限制都在Metaspace里.
如你所见, Metaspace 大小需求取决于加载的类的数量和这些类声明的大小. 所以, 很明显java.lang.OutOfMemoryError: Metaspace
的主要原因是: 要不太多类, 要不太大的类被加载到Metaspace中.
4.3 案例
就如我们在之前解释的那样, Metaspace使用率与加载到JVM中的类的数量强相关. 下列代码就是最简单的例子:
public class Metaspace {
static javassist.ClassPool cp = javassist.ClassPool.getDefault();
public static void main(String[] args throws Exception {
for (int i=0; ;i++) {
Class c = cp.makeClass("eu.plumbr.demo.Generated" + i).toClass();
}
}
}
在这个例子中, 源代码循环遍历一个循环并在运行时生成类. 所有这些生成的类定义在持续地消耗Metaspace. javassist
库对类生成的复杂性进行了处理.
代码会持续生成新的类, 并加载他们的定义到Metaspace中直到空间被完全占满, java.lang.OutOfMemoryError: Metaspace
抛出. 当在Mac OS X, Java 1.8.0_05上使用-XX:MaxMetaspaceSize=64m
大概加载70,000个类会死掉.
4.4 解决方案
第一个解决因为Metaspace内存溢出的方案很明显. 如果应用耗尽了Metaspace的内存, 你应该增加Metaspace的大小. 调整应用运行配置, 调整下列参数:
-XX:MaxMetaspaceSize=512m
上述配置案例告诉JVM, Metaspace允许在抛出OutOfMemoryError的错误之前增长到512MB.
另一个解决方案乍一看更容易. 你可以通过删除这个参数移除Metaspace的限制. 但是需要注意的是, 你这么做, 可能会导致swap的大量消耗和/或导致本机物理内存分配失败.
通过使用上述建议的 “快速修复”, 你只会通过隐藏 java.lang.OutOfMemoryError: Metaspace
掩盖症状, 而不是从根本上解决问题.
如果应用程序内存泄漏或是加载的不合理的东西到Metaspace, 上述解决方案实际上不会改善任何事情, 它只会推迟问题。