一. 景象

前段时间公司线上环境的一个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诊断工具(Java在线诊断利器之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秒),公布到生产环境验证了下,公布后就降下来了,到明天为止基本上趋于稳定

调整后基本上没有再呈现稳定

三. 总结

  1. 目前次要是通过批改JVM的-XX:SoftRefLRUPolicyMSPerMB值来解决metaspace回升问题,后续会继续察看变动,适当调整参数。至于这个参数之前为什么会被设置成0, 还须要找ops确认下。
  2. 咱们的利用须要大量RPC交互,属于I/O密集型业务,应用SOA,Dubbo都会遇到相似的问题,通过下面的源码剖析能够看出这个是无奈防止的(除非是换一种序列化协定,比方hessian,不走办法反射的形式来赋值)包含自身应用的Spring框架很多中央也是通过反射实现的比方AOP,还有咱们埋点常常应用的JsonUtils工具,通过dump文件也能看进去存在大量的属性拷贝和反射操作。

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

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

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

或者应用MapStruct这种帮你生成setget办法的工具,成果会更好。

文章起源:http://javakk.com/160.html