一. 现象
前段时间公司线上环境的一个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秒),发布到生产环境验证了下,发布后就降下来了,到今天为止基本上趋于稳定
调整后基本上没有再出现波动
三. 总结
所以我们在平时的业务代码开发中如果遇到两个对象赋值的操作尽量少用反射的方式实现,比如下面的代码:
这里做的对象拷贝操作使用的是apache common-beanutils.jar中的BeanUtils,这个类底层采用javabeans+反射实现,性能比较差,内存开销比较大,当系统高并发的情况容易导致Metaspace空间增长过快,不建议这样使用。
如果字段少的话直接赋值就行了,多的话可以使用Cglib的BeanCopier类,BeanCopier类底层是采用asm字节码操作方式来进行对象拷贝操作,性能损耗和内存开销都比较小。
或者使用MapStruct这种帮你生成set、get方法的工具,效果会更好。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持小牛知识库。
本文向大家介绍linux解决Tomcat内存溢出的问题,包括了linux解决Tomcat内存溢出的问题的使用技巧和注意事项,需要的朋友参考一下 Tomcat本身不能直接在计算机上运行,需要依赖于操作系统和一个JAVA虚拟机。JAVA程序启动时JVM会分配一个初始内存和最大内存给程序。当程序需要的内存超出内存的最大值时虚拟机就会提示内存溢出,并且导致应用服务崩溃。 一、常见的Java内存溢出有以下三
本文向大家介绍JAVA内存溢出解决方案图解,包括了JAVA内存溢出解决方案图解的使用技巧和注意事项,需要的朋友参考一下 这篇文章主要介绍了JAVA内存溢出解决方案图解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 1.在apache-tomcat-7.0.70\bin\catalina.bat(Linux 系统则在catalina.sh) 文件下
本文向大家介绍解决Tensorflow sess.run导致的内存溢出问题,包括了解决Tensorflow sess.run导致的内存溢出问题的使用技巧和注意事项,需要的朋友参考一下 下面是调用模型进行批量测试的代码(出现溢出),开始以为导致溢出的原因是数据读入方式问题引起的,用了tf , PIL和cv等方式读入图片数据,发现越来越慢,内存占用飙升,调试时发现是sess.run这里出了问题(随着f
本文向大家介绍解决golang内存溢出的方法,包括了解决golang内存溢出的方法的使用技巧和注意事项,需要的朋友参考一下 最近在项目中出现golang内存溢出的问题,master刚开始运行时只有10多M,运行几天后,竟然达到了10多个G。而且到凌晨流量变少内存也没有明显降低,内存状态呈现一种很不健康的曲线。 像这种情况肯定是golang内存溢出了,为此我持续排查了两天,终于找到问题所在,特此记录
本文向大家介绍Android 内存溢出和内存泄漏的问题,包括了Android 内存溢出和内存泄漏的问题的使用技巧和注意事项,需要的朋友参考一下 Android 内存溢出和内存泄漏的问题 在面试中,经常有面试官会问“你知道什么是内存溢出?什么是内存泄漏?怎么避免?”通过这篇文章,你可以回答出来了。 内存溢出 (OOM)是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;
本文向大家介绍PHPExcel内存泄漏问题解决方法,包括了PHPExcel内存泄漏问题解决方法的使用技巧和注意事项,需要的朋友参考一下 使用 PHPExcel 来生成 excel 文档是比较消耗内存的,有时候可能会需要通过一个循环来把大数据切分成若干个小的 excel 文档保存来避免内存耗尽。 然而 PHPExcel 存在 circular references 的情况(貌似在最新的 1.6.5