记一次Metaspace导致频繁fgc的问题排查过程

最近线上有一条机器在运行了10几天后出现告警,频繁出现fgc,在切断流量之后,从运维那边拿了应用的heapdump文件。
在一开始出现fgc时,我就上了容器平台查看了gc日志,gc日志如下:


image.png

从日志中可以看出很明显优于metaspace空间不够造成的fgc,而且不断进行fgc,且metaspace空间回收不了。于是查看一下jvm启动参数,参数如下:


image.png

这里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的代码,代码片段如下:

privateMap<String, Invoker> setMethods= newHashMap<String, Invoker>();
privateMap<String, Invoker> getMethods= newHashMap<String, Invoker>();
privateMap<String, Class<?>> setTypes= newHashMap<String, Class<?>>();
privateMap<String, Class<?>> getTypes= newHashMap<String, Class<?>>();

这个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方法:

image.png

Method.acquireMethodAccessor方法:


image.png

ReflectionFactory.newMethodAccessor方法:


image.png

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方法片段:

image.png

ClassDefiner.defineClass方法:

image.png

另外还有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,运行结果如下:
不设置的情况:

image.png

设置为0的情况:


image.png

可以看出两种设置下Metaspace内存占用相差很大,基本验证分析的结果是正确的。
最终针对这次因为Metaspace引起频繁fgc的修复的方案可以有:

  • 增大Metaspace空间
  • 牺牲一些性能,应用启动参数中添加参数-Dsun.reflect.inflationThreshold,并将其值设置的足够大。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容

  • jvm原理 Java虚拟机是整个java平台的基石,是java技术实现硬件无关和操作系统无关的关键环节,是java...
    AI乔治阅读 17,208评论 21 486
  • 前言: 由于最近写的程序在运行一段时间后出现高cpu,然后不可用故进而进行排查,最终定位到由于metaspace引...
    Michael_xlp阅读 22,180评论 6 17
  • 一,apk以进程的形式运行,进程的创建是由zygote。 参考文章《深入理解Dalvik虚拟机- Android应...
    Kevin_Junbaozi阅读 2,810评论 0 12
  • (1) 和S蹲在街头,烤几串儿羊肉串,喝几瓶啤酒,在烤死人的天气里吃烧烤,酣畅的大汗淋漓。闲谈八卦间,几瓶啤酒已下...
    廖焱阅读 435评论 0 1
  • 好久没有打过篮球了,大学操场的橡胶皮味的也两年多没闻过了,曾经毕了业说要周游世界,后来就没再想过了,上学时候虽然是...
    王神马阅读 153评论 0 0