JVM Metaspace内存溢出问题解决方案

一. 现象

前段时间公司线上环境的一个Java应用因为OOM的异常报警,导致整个服务不可用被拉出集群,本地模拟重现的现象如下:

当时的解决方案是增加metaspace的容量:-XX:MaxMetaspaceSize=500m,从原来默认的256m改为500m,虽然没有再出现oom,但这个只是临时解决方案,通过公司的监控系统观察metaspace的使用情况还是在上升,而且后面随着业务访问量越来越大还是有可能达到阈值。

二. 分析

Metaspace元空间主要是存储类的元数据信息,我们的应用里加载的各种类描述信息,比如类名、属性、方法、访问限制等,按照一定的结构存储在Metaspace里。

由此可知metaspace空间增长是由于反射类加载,动态代理生成的类加载等导致的,也就是说Metaspace的大小和加载类的数据有关系,加载的类越多metaspace占用的内存也就越大。

因为了解当时的业务场景是因为有个邮件服务访问订单详情接口的访问量突然上升,以及查看log的eroor日志发现大部分都是订单详情接口先报出的这个问题:java.lang.OutOfMemoryError: Metaspace

这里我在测试环境Java应用的jvm里增加-XX:+TraceClassLoading -XX:+TraceClassUnloading记录下类的加载和卸载情况,然后通过jmeter多个线程调用订单详情接口模拟metaspace溢出的现象,发现在catalina.out文件里输出的除了业务上用到的类外还有大量的反射类,如下:

这些反射类被频繁的加载和卸载是不正常的,通过Arthas诊断工具观察调用链发现每次调用接口都是通过反射的方式实现的。

目前我们的项目都是基于SOA框架对外提供访问的,从上图sun.reflect的调用者也能看出来

通过上图可以看出在调用底层接口时都是通过反射的方式获取类的实例,查看框架底层代码实现可以确认

同样对底层接口返回的json数据反序列化时也会用到反射


继续跟代码可以看到这些反射的实现都会用到java.lang.Class里的ReflectionData对象

ReflectionData是个内部静态类被缓存起来,里面的属性就是我们做反射操作时需要用的属性Field,方法Method和构造函数等。但是有个问题reflectionData是被SoftReference软引用修饰的,如下图

如果是软引用的话在内存空间不足时就可能会被回收掉,如果回收掉那下次再使用的话只能重新通过反射获取。

而SoftReference是否被回收又跟SoftRefLRUPolicyMSPerMB参数的值有关系,查看我们线上JVM的配置发现XX:SoftRefLRUPolicyMSPerMB这个参数设置的是0

SoftRefLRUPolicyMSPerMB这个参数大概意思是每1M空闲空间可保持的SoftReference对象的生存时长(单位是ms毫秒),LRU是Least Recently Used的缩写,最近最少使用的。

这个值jvm默认是1000ms,如果被设置为0,就会导致软引用对象马上被回收掉,进而会导致重新频繁的生成新的类,而无法达到复用的效果。

上图里大量的sun.reflect.GeneratedSerializationConstructorAccessor,GeneratedMethodAccessor就是这样产生的。

我把这个参数改回默认值-XX:SoftRefLRUPolicyMSPerMB=1000 (1秒),发布到生产环境验证了下,发布后就降下来了,到今天为止基本上趋于稳定

调整后基本上没有再出现波动

三. 总结

  • 目前主要是通过修改JVM的-XX:SoftRefLRUPolicyMSPerMB值来解决metaspace上升问题,后续会持续观察变化,适当调整参数。至于这个参数之前为什么会被设置成0, 还需要找ops确认下。
  • 我们的应用需要大量RPC交互,属于I/O密集型业务,使用SOA,Dubbo都会遇到类似的问题,通过上面的源码分析可以看出这个是无法避免的(除非是换一种序列化协议,比如hessian,不走方法反射的方式来赋值)包括本身使用的Spring框架很多地方也是通过反射实现的比如AOP,还有我们埋点经常使用的JsonUtils工具,通过dump文件也能看出来存在大量的属性拷贝和反射操作。

所以我们在平时的业务代码开发中如果遇到两个对象赋值的操作尽量少用反射的方式实现,比如下面的代码:

这里做的对象拷贝操作使用的是apache common-beanutils.jar中的BeanUtils,这个类底层采用javabeans+反射实现,性能比较差,内存开销比较大,当系统高并发的情况容易导致Metaspace空间增长过快,不建议这样使用。

如果字段少的话直接赋值就行了,多的话可以使用Cglib的BeanCopier类,BeanCopier类底层是采用asm字节码操作方式来进行对象拷贝操作,性能损耗和内存开销都比较小。

或者使用MapStruct这种帮你生成set、get方法的工具,效果会更好。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • 完美解决java读取大文件内存溢出的问题

    1. 传统方式:在内存中读取文件内容 读取文件行的标准方式是在内存中读取,Guava 和Apache Commons IO都提供了如下所示快速读取文件行的方法: Files.readLines(new File(path), Charsets.UTF_8); FileUtils.readLines(new File(path)); 实际上是使用BufferedReader或者其子类LineNumberReader来读取的. 传统方式的问题: 是文件的所有行都被存放在内存中,当文件足够大时很快就会

  • Java编程常见内存溢出异常与代码示例

    Java 堆是用来存储对象实例的, 因此如果我们不断地创建对象, 并且保证 GC Root 和创建的对象之间有可达路径以免对象被垃圾回收, 那么当创建的对象过多时, 会导致 heap 内存不足, 进而引发 OutOfMemoryError 异常. /** * @author xiongyongshun * VM Args: java -Xms10m -Xmx10m -XX:+HeapDumpOnOutOfMemoryError */ public class OutOfMemoryErrorTe

  • Java虚拟机内存溢出与内存泄漏

    一.基本概念 内存溢出:简单地说内存溢出就是指程序运行过程中申请的内存大于系统能够提供的内存,导致无法申请到足够的内存,于是就发生了内存溢出. 内存泄漏:内存泄漏指程序运行过程中分配内存给临时变量,用完之后却没有被GC回收,始终占用着内存,既不能被使用也不能分配给其他程序,于是就发生了内存泄漏. 内存溢出 out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory: 内存泄露 memory leak,是指程序在申请内存后,无法释放已申请的内存空间

  • Java内存区域与内存溢出异常详解

    Java内存区域与内存溢出异常 概述 对于 C 和 C++程序开发的开发人员来说,在内存管理领域,程序员对内存拥有绝对的使用权,但是也要主要到正确的使用和清理内存,这就要求程序员有较高的水平. 而对于 Java 程序员来说,在虚拟机的自动内存管理机制的帮助下,不再需要为每一个 new 操作去写配对的 delete/free 代码,而且不容易出现内存泄漏和内存溢出问题,看起来由虚拟机管理内存一切都很美好.不过,也正是因为 Java 程序员把内存控制的权力交给了 Java 虚拟机,一旦出现内存泄漏和

  • 解决Java导入excel大量数据出现内存溢出的问题

    问题:系统要求导入40万条excel数据,采用poi方式,服务器出现内存溢出情况. 解决方法:由于HSSFWorkbook workbook = new HSSFWorkbook(path)一次性将excel load到内存中导致内存不够. 故采用读取csv格式.由于csv的数据以x1,x2,x3形成,类似读取txt文档. private BufferedReader bReader; /** * 执行文件入口 */ public void execute() { try { if(!path.

  • java虚拟机内存溢出及泄漏实例

    测试参数设置: 1.循环调用new A()实现堆溢出,java.lang.OutOfMemoryError: Java heap space, 虚拟机参数:-Xms1M -Xmx1M -XX:+HeapDumpOnOutOfMemoryError,解释:将-Xmx和-Xms设置为一样可以避免堆自动扩展,-XX:+HeapDumpOnOutOfMemoryError可以让虚拟机在出现内存溢出异常时Dump出当前的堆内存转储快照 // while (true){ // new A().do2();

  • 详解Java内存溢出的几种情况

    JVM(Java虚拟机)是一个抽象的计算模型.就如同一台真实的机器,它有自己的指令集和执行引擎,可以在运行时操控内存区域.目的是为构建在其上运行的应用程序提供一个运行环境.JVM可以解读指令代码并与底层进行交互:包括操作系统平台和执行指令并管理资源的硬件体系结构. 1. 前言 JVM提供的内存管理机制和自动垃圾回收极大的解放了用户对于内存的管理,大部分情况下不会出现内存泄漏和内存溢出问题.但是基本不会出现并不等于不会出现,所以掌握Java内存模型原理和学会分析出现的内存溢出或内存泄漏,对于使用J

  • Java8内存模型PermGen Metaspace实例解析

    一.JVM 内存模型 根据 JVM 规范,JVM 内存共分为虚拟机栈.堆.方法区.程序计数器.本地方法栈五个部分. 1.虚拟机栈:每个线程有一个私有的栈,随着线程的创建而创建.栈里面存着的是一种叫"栈帧"的东西,每个方法会创建一个栈帧,栈帧中存放了局部变量表(基本数据类型和对象引用).操作数栈.方法出口等信息.栈的大小可以固定也可以动态扩展.当栈调用深度大于JVM所允许的范围,会抛出StackOverflowError的错误,不过这个深度范围不是一个恒定的值,我们通过下面这段程序可以测

  • JVM Metaspace内存溢出问题解决方案

    一. 现象 前段时间公司线上环境的一个Java应用因为OOM的异常报警,导致整个服务不可用被拉出集群,本地模拟重现的现象如下: 当时的解决方案是增加metaspace的容量:-XX:MaxMetaspaceSize=500m,从原来默认的256m改为500m,虽然没有再出现oom,但这个只是临时解决方案,通过公司的监控系统观察metaspace的使用情况还是在上升,而且后面随着业务访问量越来越大还是有可能达到阈值. 二. 分析 Metaspace元空间主要是存储类的元数据信息,我们的应用里加载的

  • Python内存泄漏和内存溢出的解决方案

    一.内存泄漏 像Java程序一样,虽然Python本身也有垃圾回收的功能,但是同样也会产生内存泄漏的问题. 对于一个用 python 实现的,长期运行的后台服务进程来说,如果内存持续增长,那么很可能是有了"内存泄露". 1.内存泄露的原因 对于 python 这种支持垃圾回收的语言来说,怎么还会有内存泄露? 概括来说,有以下三种原因: 所用到的用 C 语言开发的底层模块中出现了内存泄露. 代码中用到了全局的 list. dict 或其它容器,不停的往这些容器中插入对象,而忘记了在使用完

  • 老生常谈JVM的内存溢出说明及参数调整

    对于JVM的内存写过的文章已经有点多了,而且有点烂了,不过说那么多大多数在解决OOM的情况,于此,本文就只阐述这个内容,携带一些分析和理解和部分扩展内容,也就是JVM宕机中的一些问题,OK,下面说下OOM的常见情况: 第一类内存溢出,也是大家认为最多,第一反应认为是的内存溢出,就是堆栈溢出: 那什么样的情况就是堆栈溢出呢?当你看到下面的关键字的时候它就是堆栈溢出了: java.lang.OutOfMemoryError: ......java heap space..... 也就是当你看到hea

  • Spring Cloud Gateway 内存溢出的解决方案

    记 Spring Cloud Gateway 内存溢出查询过程 环境配置: org.springframework.boot : 2.1.4.RELEASE org.springframework.cloud :Greenwich.SR1 事故记录: 由于网关存在 RequestBody 丢失的情况,顾采用了网上的通用解决方案,使用如下方式解决: @Bean public RouteLocator tpauditRoutes(RouteLocatorBuilder builder) { retu

  • 了解Java虚拟机JVM的基本结构及JVM的内存溢出方式

    JVM内部结构图 Java虚拟机主要分为五个区域:方法区.堆.Java栈.PC寄存器.本地方法栈.下面 来看一些关于JVM结构的重要问题. 1.哪些区域是共享的?哪些是私有的? Java栈.本地方法栈.程序计数器是随用户线程的启动和结束而建立和销毁的, 每个线程都有独立的这些区域.而方法区.堆是被整个JVM进程中的所有线程共享的. 2.方法区保存什么?会被回收吗? 方法区不是只保存的方法信息和代码,同时在一块叫做运行时常量池的子区域还 保存了Class文件中常量表中的各种符号引用,以及翻译出来的

  • docker启动ES内存溢出的解决方案

    在elasticsearch的config中加jvm.options文件,修改堆栈大小,默认是2GB,直接启动es即可,保证之前已经映射了配置文件. -Xms5g -Xmx5g 完整jvm.options文件如下: ## JVM configuration ################################################################ ## IMPORTANT: JVM heap size ############################

  • Java 堆内存溢出原因分析

    前言 任何使用过基于 Java 的企业级后端应用的软件开发者都会遇到过这种低劣.奇怪的报错,这些报错来自于用户或是测试工程师: java.lang.OutOfMemoryError:Java heap space. 为了弄清楚问题,我们必须返回到算法复杂性的计算机科学基础,尤其是"空间"复杂性.如果我们回忆,每一个应用都有一个最坏情况特征.具体来说,在存储维度方面,超过推荐的存储将会被分配到应用程序上,这是不可预测但尖锐的问题.这导致了堆内存的过度使用,因此出现了"内存不够&

  • JAVA 内存溢出案例汇总

    写在前面 作为程序员,多多少少都会遇到一些内存溢出的场景,如果你还没遇到,说明你工作的年限可能比较短,或者你根本就是个假程序员!哈哈,开个玩笑.今天,我们就以Java代码的方式来列举几个典型的内存溢出案例,希望大家在日常工作中,尽量避免写这些low水平的代码. 定义主类结构 首先,我们创建一个名称为BlowUpJVM的类,之后所有的案例实验都是基于这个类进行.如下所示. public class BlowUpJVM { } 栈深度溢出 public static void testStackOv

  • 以Java代码的方式总结几个典型的内存溢出案例

    一.图示 我们先来看看今天要介绍哪些内存溢出案例,这里总结了一张图,如下所示. 二.定义主类结构 首先,我们创建一个名称为BlowUpJVM的类,之后所有的案例实验都是基于这个类进行.如下所示. public class BlowUpJVM { } 三.栈深度溢出 public static void testStackOverFlow(){ BlowUpJVM.testStackOverFlow(); } 栈不断递归,而且没有处理,所以虚拟机栈就不断深入不断深入,栈深度就这样溢出了. 四.永久

随机推荐