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(); } } } JAVA |
在这个例子中, 源代码循环遍历一个循环并在运行时生成类. 所有这些生成的类定义在持续地消耗 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, 上述解决方案实际上不会改善任何事情, 它只会推迟问题。