java.lang.OutOfMemoryError: Metaspace 的解决

文章目录

  • 1. 起因
  • 2. Metaspace 的使用机制
  • 3. 解决方式
  • 4. Metaspace 参数汇总
  • 5. 总结

1. 起因

组内一个运营服务设计之初承载的业务数据量比较小,随着服务功能逐渐扩展业务量快速增长,最近时常出问题,甚至在告警中报出了 java.lang.OutOfMemoryError: Metaspace 错误

我们都知道 Metaspace 是 Java 8 以后的方法区实现,主要存储的就是JVM 加载到内存中的类相关数据,以及即时编译的代码等。很显然,这个错误的原因大概率是加载到内存中的 class 占用的内存超过了 Metaspace 的限制

2. Metaspace 的使用机制

在解决这个错误之前,我们首先来了解下 Metaspace 的使用机制。通常 JVM 加载类时,会进行以下操作最终将类加载进内存:

  1. 通过一个类的全限定名来获取其定义的二进制字节流
  2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构,也就是存储在 Metaspace 中
  3. 在Java堆中生成一个代表这个类的 java.lang.Class对象,作为对方法区中这些数据的访问入口

当 Metaspace 空间不足的时候,会触发 Full GC 卸载一些无用的类以便回收内存,但是如我们所知,类的卸载条件是很苛刻的(参考方法区垃圾回收),这样就会发生一种情况,那就是即便进行了 Full GC,JVM 也无法回收到足够的内存以存储新加载的类,最终导致其占用的内存超过限制,只能报出 OOM 错误

Metaspace 空间不足触发 Full GC 这个点只要多执行几次 jstat -gcutil pid 命令,观察 Full GC 的次数变化就能看出来,需注意 Full GC 非常影响程序效率,应该避免

3. 解决方式

通过以上分析,我们很容易就能想到解决方法:

  1. Metaspace 空间不足,那么增大 Metaspace 的空间就可以了,这种方式简单粗暴,但是高效
  2. Metaspace 空间不足大概率是因为加载的类太多,而受限于苛刻的类卸载条件无法将其回收掉,那么最好的解决方式就是不加载那些不需要的类。基于这个思路,检查服务是否加载了过多的类,将其找出来,去除掉多余的加载项。这种方式治标治本,但是比较耗时

最终出于效率考虑,我们选择了增大 Metaspace 的空间的解决方法,以下是两种处理方式:

  1. 在服务的启动参数中将 MaxMetaspaceSize 最大元空间内存从 128m 增大到 256m,服务启动后运行一段时间问题不再复现

    -XX:MaxMetaspaceSize=256m
    
  2. 直接去掉 -XX:MaxMetaspaceSize 启动参数,不限制 Metaspace 内存的大小。这种方式需要注意,假若机器物理内存不足,有可能会引起内存交换(swapping),严重拖累系统性能,还可能造成 native 内存分配失败等问题

4. Metaspace 参数汇总

参数 含义
-XX:MetaspaceSize=N 初始化的 Metaspace 大小,该值越大触发 Metaspace 区域 GC 的时机就越晚。随着GC的到来,JVM 会根据实际情况调控 Metaspace 的大小,可能增加上限也可能降低
-XX:MaxMetaspaceSize=N 限制 Metaspace 内存的最大值,防止因为某些情况导致 Metaspace 无限使用本地内存,影响到其他程序
-XX:MinMetaspaceFreeRatio=N 当进行过 Metaspace 区域 GC 之后,JVM 会计算当前 Metaspace 的空闲空间比,如果空闲比小于这个参数,那么 JVM 将增大 Metaspace 的内存大小。该参数的默认值为40,也就是40%。设置这个参数可以控制 Metaspace 的增长的速度,太小的值会导致 Metaspace 增长缓慢,Metaspace 的使用逐渐趋于饱和,可能会影响之后类的加载。而太大的值会导致 Metaspace 增长过快,浪费内存
-XX:MaxMetasaceFreeRatio=N 当进行过 Metaspace 区域的GC之后, 会计算当前 Metaspace 的空闲空间比,如果空闲比大于这个参数,那么虚拟机会释放 Metaspace 的部分空间。该参数的默认值为70,也就是70%
-XX:MaxMetaspaceExpansion=N Metaspace 增长时的最大幅度,参数的默认值为5452592B(大约为5MB)
-XX:MinMetaspaceExpansion=N Metaspace 增长时的最小幅度,该参数的默认值为340784B(大约为330KB)

5. 总结

Metaspace 是垃圾回收率比较低的 JVM 堆外内存区域,总结来说,其占用内存过多通常有以下两种原因:

  1. 引用的 jar 包加载了很多 class
    这种情况需要排查服务中引用的比较大的 jar 包,去除掉多余的加载项,对于那些不能去掉的,按照最小粒度去加载
  2. 动态生成类过多
    Java 的许多框架会大量使用动态代理等技术动态生成类,使用这样的方式创建对象可能会有大量动态类产生,元空间消耗会增加,这种情况似乎没有好的解决方法

你可能感兴趣的:(JVM,随笔,Java,基础,java,jvm,Metaspace)