十年网站开发经验 + 多家企业客户 + 靠谱的建站团队
量身定制 + 运营维护+专业推广+无忧售后,网站问题一站解决
最近线上有一条机器在运行了10几天后出现告警,频繁出现fgc,在切断流量之后,从运维那边拿了应用的heapdump文件。在一开始出现fgc时,我就上了容器平台查看了gc日志,gc日志如下:
创新互联建站专业为企业提供上犹网站建设、上犹做网站、上犹网站设计、上犹网站制作等企业网站建设、网页设计与制作、上犹企业网站模板建站服务,十多年上犹做网站经验,不只是建网站,更提供有价值的思路和整体网络服务。从日志中可以看出很明显优于metaspace空间不够造成的fgc,而且不断进行fgc,且metaspace空间回收不了。于是查看一下jvm启动参数,参数如下:
这里Metaspace和MaxMetaspace都设置成了256M,奇怪了gc日志中Metaspace才使用了165M就出现了fgc,难道是新加载的类90M的空间吗,这个可以肯定不是,如果不是新申请90M的空间这个原因引起的,那么就只有metaspace内存碎片引起的了。于是通过mat分析heapdump,发现 DelegatingClassLoader有1100多个,于是先查看一下 DelegatingClassLoader是个什么东西?其属于sun.reflect包下,代码如下:
classDelegatingClassLoader extendsClassLoader { DelegatingClassLoader(ClassLoader var1) { super(var1); }
证明其确实一个ClassLoader。
那到底是什么对象在引用这些ClassLoader呢,通过mat发现是 GeneratedMethodAccessor在引用这些ClassLoader,继续跟踪发现是mybatis的Reflector应用了这些对象。好办了,于是继续查看了Reflector的代码,代码片段如下:
privateMapsetMethods= newHashMap ();privateMap getMethods= newHashMap ();privateMap > setTypes= newHashMap >();privateMap > getTypes= newHashMap >();
这个Reflector对象会缓存orm中实体类的getter setter方法,mybatis需要将表中的记录转换成java实体类,为了提高反射的效率将实体类的方法、构造函数等缓存起来了,Mybatis会在运行的过程中通过 ReflectorFactory为每一个实体类创建一个 Reflector方便后续进行反射调用。
问题来了,为什么会有这么多的 DelegatingClassLoader呢?通过mat可以分析出来,这些ClassLoader最终都是被java的 Method对象所引用的。
于是分析Method的创建过程和Method的调用过程,最终发现Method在调用过程会创建一个MethodAccessor并将MehtodAccessor作为存在一个叫做methodAccessor的field中,java为了提高反射调用的性能,用了一种膨胀(inflation)的方式(从jni调用转换成classbytes调用),通过参数-Dsun.reflect.inflationThreshold进行控制默认15,在小于这个次数时会使用native的方式对方法进行调用,如果method的调用次数超过指定次数就会使用字节码的方式生成方法调用,如果使用字节码的方式最终会为每一个方法都生成 DelegatingClassLoader。具体的源码如下:Method.invoke方法:
Method.acquireMethodAccessor方法:
ReflectionFactory.newMethodAccessor方法:
NativeMethodAccessorImpl.invoke方法:
publicObject invoke(Object var1, Object[] var2) throwsIllegalArgumentException, InvocationTargetException { if(++this.numInvocations > ReflectionFactory.inflationThreshold() && !ReflectUtil.isVMAnonymousClass(this.method.getDeclaringClass())) { MethodAccessorImpl var3 = (MethodAccessorImpl)(newMethodAccessorGenerator()).generateMethod(this.method.getDeclaringClass(), this.method.getName(), this.method.getParameterTypes(), this.method.getReturnType(), this.method.getExceptionTypes(), this.method.getModifiers()); this.parent.setDelegate(var3); } returninvoke0(this.method, var1, var2);}
MethodAccessorGenerator.generateMethod方法片段:
ClassDefiner.defineClass方法:
另外还有RefectionFactory的checkInitted方法会通过 System.getProperty方法拿 sun.reflect.inflationThresholdproperty,默认值为15。代码的流程不是很长,切比较容易理解。接下来就是验证是不是java反射的Inflat方式引起的。于是写了下面的例子进行验证:
/-XX:MetaspaceSize=64M -XX:MaxMetaspaceSize=64M -Xms1g -Xmx1g -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -XX:+PrintGCTimeStamps-XX:+PrintGCDetails -Dsun.reflect.inflationThreshold=0/public static voidmain(String[] args) throwsIOException, InvocationTargetException, IllegalAccessException { ReflectorFactory reflectorFactory = newDefaultReflectorFactory(); System.out.println("load class start"); // model有1000个方法Reflector reflector1 = reflectorFactory.findForClass(TestModel.class); Reflector reflector2 = reflectorFactory.findForClass(TestModel2.class); Reflector reflector3 = reflectorFactory.findForClass(TestModel3.class); System.out.println("load class finished"); // model有1000个方法TestModel testModel = newTestModel(); Object[] empty = {}; Object[] one1 = {"a"}; TestModel2 testModel2 = newTestModel2(); TestModel3 testModel3 = newTestModel3(); System.out.println("method invoke start"); for(inti = 0; i < 1; i++) { for(intj = 0; j < 1000; j++) { reflector1.getSetInvoker("field"+ j).invoke(testModel, one1); reflector1.getGetInvoker("field"+ j).invoke(testModel, empty); reflector2.getSetInvoker("field"+ j).invoke(testModel2, one1); reflector2.getGetInvoker("field"+ j).invoke(testModel2, empty); reflector3.getSetInvoker("field"+ j).invoke(testModel3, one1); reflector3.getGetInvoker("field"+ j).invoke(testModel3, empty); } } System.out.println("method invoke finished"); System.in.read();}
通过不设置参数 sun.reflect.inflationThreshold和设置参数为0,运行结果如下:不设置的情况:
设置为0的情况:
可以看出两种设置下Metaspace内存占用相差很大,基本验证分析的结果是正确的。最终针对这次因为Metaspace引起频繁fgc的修复的方案可以有:
增大Metaspace空间
牺牲一些性能,应用启动参数中添加参数 -Dsun.reflect.inflationThreshold,并将其值设置的足够大。
读者福利
加微信:haolagui521备注51CTO领取附送学习进阶架构资料、PDF书籍文档、面试资料
另外有需要云服务器可以了解下创新互联scvps.cn,海内外云服务器15元起步,三天无理由+7*72小时售后在线,公司持有idc许可证,提供“云服务器、裸金属服务器、高防服务器、香港服务器、美国服务器、虚拟主机、免备案服务器”等云主机租用服务以及企业上云的综合解决方案,具有“安全稳定、简单易用、服务可用性高、性价比高”等特点与优势,专为企业上云打造定制,能够满足用户丰富、多元化的应用场景需求。