OOM系列之四:java.lang.OutOfMemoryError: 元空间问题详解转载
此文来自于plumbr官网,plumbr作为一个常用的JVM 监测工具,官网有完整的oom和gc文章,准备慢慢全部翻译过来:
第一篇:java.lang.OutOfMemoryError:Java heap space
第二篇:Java.lang.OutOfMemoryError: GC overhead limit exceeded
第三篇:java.lang.OutOfMemoryError: 永久空间
第四篇:java.lang.OutOfMemoryError: 元空间
正文:
Java 应用程序只能使用有限的内存量。您的特定应用程序可以使用的确切内存量是在应用程序启动期间指定的。为了让事情变得更复杂,Java 内存被分成不同的区域,如下图所示:
所有这些区域的大小,包括元空间区域,都可以在 JVM 启动期间指定。如果您不自己确定大小,将使用特定于平台的默认值。
所述java.lang.OutOfMemoryError:元空间中消息指示所述元空间区域在存储器中被耗尽。
1,是什么原因造成的?
如果您不是 Java 领域的新手,您可能熟悉 Java 内存管理中的另一个概念,称为 PermGen。从 Java 8 开始,Java 中的内存模型发生了重大变化。引入了一个名为 Metaspace 的新内存区域,并删除了 Permgen。做出此更改的原因有多种,包括但不限于:
- permgen 所需的大小很难预测。它导致供应不足触发java.lang.OutOfMemoryError: Permgen size错误或过度供应导致资源浪费。
- GC 性能改进,启用并发类数据取消分配,无需GC 暂停和元数据上的特定迭代器
- 支持进一步优化,例如G1并发类卸载。
因此,如果您熟悉 PermGen,那么您需要了解的背景知识就是 – Java 8 之前 PermGen 中的任何内容(类的名称和字段、类的方法以及方法的字节码、常量池、JIT 优化等) ) – 现在位于元空间。
如您所见,元空间大小要求取决于加载的类的数量以及此类声明的大小。所以很容易看到的主要原因java.lang.OutOfMemoryError:元空间是:太多的级别或过大的类加载到元空间。
2,举个例子
正如我们在前一章中解释的那样,元空间的使用与加载到 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被抛出。当使用-XX:MaxMetaspaceSize=64m启动时,在 Mac OS X 上,我的 Java 1.8.0_05 在加载了大约 70,000 个类时死亡。
3,解决办法是什么?
面对由于 Metaspace 导致的 OutOfMemoryError 时,第一个解决方案应该是显而易见的。如果应用程序耗尽了内存中的 Metaspace 区域,您应该增加 Metaspace 的大小。更改您的应用程序启动配置并增加以下内容:
-XX:MaxMetaspaceSize=512m
上面的配置示例告诉 JVM,允许 Metaspace 增长到 512 MB,然后才能开始以OutOfMemoryError的形式抱怨。
乍一看,另一种解决方案甚至更简单。您可以通过删除此参数来完全取消对元空间大小的限制。但请注意,这样做可能会引入大量交换和/或导致本机分配失败。
不过,在结束之前,请注意 - 通常情况下,通过使用上述推荐的“快速修复”,您最终会通过隐藏java.lang.OutOfMemoryError: Metaspace而没有解决潜在问题来掩盖症状。如果您的应用程序泄漏内存或只是将一些不合理的内容加载到 Metaspace 中,上述解决方案实际上不会改善任何事情,只会推迟问题。