最近線上有一條機(jī)器在運(yùn)行了10幾天后出現(xiàn)告警,頻繁出現(xiàn)fgc券册,在切斷流量之后蚣常,從運(yùn)維那邊拿了應(yīng)用的heapdump文件市咽。
在一開(kāi)始出現(xiàn)fgc時(shí),我就上了容器平臺(tái)查看了gc日志史隆,gc日志如下:
從日志中可以看出很明顯優(yōu)于metaspace空間不夠造成的fgc魂务,而且不斷進(jìn)行fgc,且metaspace空間回收不了泌射。于是查看一下jvm啟動(dòng)參數(shù)粘姜,參數(shù)如下:
這里Metaspace和MaxMetaspace都設(shè)置成了256M,奇怪了gc日志中Metaspace才使用了165M就出現(xiàn)了fgc熔酷,難道是新加載的類90M的空間嗎孤紧,這個(gè)可以肯定不是,如果不是新申請(qǐng)90M的空間這個(gè)原因引起的拒秘,那么就只有metaspace內(nèi)存碎片引起的了号显。于是通過(guò)mat分析heapdump臭猜,發(fā)現(xiàn)DelegatingClassLoader
有1100多個(gè),于是先查看一下DelegatingClassLoader
是個(gè)什么東西押蚤?其屬于sun.reflect包下蔑歌,代碼如下:
classDelegatingClassLoader extendsClassLoader {
DelegatingClassLoader(ClassLoader var1) {
super(var1);
}
證明其確實(shí)一個(gè)ClassLoader。
那到底是什么對(duì)象在引用這些ClassLoader呢揽碘,通過(guò)mat發(fā)現(xiàn)是GeneratedMethodAccessor
在引用這些ClassLoader次屠,繼續(xù)跟蹤發(fā)現(xiàn)是mybatis的Reflector應(yīng)用了這些對(duì)象。好辦了雳刺,于是繼續(xù)查看了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<?>>();
這個(gè)Reflector對(duì)象會(huì)緩存orm中實(shí)體類的getter setter方法,mybatis需要將表中的記錄轉(zhuǎn)換成java實(shí)體類掖桦,為了提高反射的效率將實(shí)體類的方法本昏、構(gòu)造函數(shù)等緩存起來(lái)了,Mybatis會(huì)在運(yùn)行的過(guò)程中通過(guò)ReflectorFactory
為每一個(gè)實(shí)體類創(chuàng)建一個(gè)Reflector
方便后續(xù)進(jìn)行反射調(diào)用枪汪。
問(wèn)題來(lái)了涌穆,為什么會(huì)有這么多的DelegatingClassLoader
呢?通過(guò)mat可以分析出來(lái)雀久,這些ClassLoader最終都是被java的Method
對(duì)象所引用的蒲犬。
于是分析Method的創(chuàng)建過(guò)程和Method的調(diào)用過(guò)程,最終發(fā)現(xiàn)Method在調(diào)用過(guò)程會(huì)創(chuàng)建一個(gè)MethodAccessor并將MehtodAccessor作為存在一個(gè)叫做methodAccessor的field中岸啡,java為了提高反射調(diào)用的性能,用了一種膨脹(inflation)的方式(從jni調(diào)用轉(zhuǎn)換成classbytes調(diào)用)赫编,通過(guò)參數(shù)-Dsun.reflect.inflationThreshold進(jìn)行控制默認(rèn)15巡蘸,在小于這個(gè)次數(shù)時(shí)會(huì)使用native的方式對(duì)方法進(jìn)行調(diào)用,如果method的調(diào)用次數(shù)超過(guò)指定次數(shù)就會(huì)使用字節(jié)碼的方式生成方法調(diào)用擂送,如果使用字節(jié)碼的方式最終會(huì)為每一個(gè)方法都生成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方法會(huì)通過(guò)System.getProperty
方法拿sun.reflect.inflationThreshold
property,默認(rèn)值為15嘹吨。
代碼的流程不是很長(zhǎng)搬味,切比較容易理解。接下來(lái)就是驗(yàn)證是不是java反射的Inflat方式引起的蟀拷。于是寫了下面的例子進(jìn)行驗(yàn)證:
/
-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個(gè)方法
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個(gè)方法
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();
}
通過(guò)不設(shè)置參數(shù)sun.reflect.inflationThreshold
和設(shè)置參數(shù)為0碰纬,運(yùn)行結(jié)果如下:
不設(shè)置的情況:
設(shè)置為0的情況:
可以看出兩種設(shè)置下Metaspace內(nèi)存占用相差很大,基本驗(yàn)證分析的結(jié)果是正確的问芬。
最終針對(duì)這次因?yàn)镸etaspace引起頻繁fgc的修復(fù)的方案可以有:
- 增大Metaspace空間
- 犧牲一些性能悦析,應(yīng)用啟動(dòng)參數(shù)中添加參數(shù)
-Dsun.reflect.inflationThreshold
,并將其值設(shè)置的足夠大此衅。