问题描述
fastjson在jetty容器中序列化HttpServletRequest会导致class泄漏,严重时会导致Meta区溢出,导致无限FGC。
fastjson序列化流程
fastjson通过动态生成代码提高序列化速度,序列化逻辑如下:
Stringserialize(Objectobject){ //先看是否已经存在对应的serializer Serializerserializer=serializerCache.get(object.getClass()); if(serializer==null){ //通过动态生成代码的方式,创建新的序列器类 Class<?>serializerClass=createSerializerClass(object.getClass()); serializer=serializerClass.newInstance(); serializerCache.put(object.getClass(),serializer); } returnserializer.serialize(object); }
如果传入的object类型是org.eclipse.jetty.server.Request,serializerClass.newInstance会出现异常。因此每次序列化org.eclipse.jetty.server.Request,都会生成一个serializerClass。即出现了class泄漏。
PS: org.eclipse.jetty.server.Request是jetty中HttpServletRequest的实现类,下面简称Request
newInstance()为什么会失败?
fastjson生成的serializerClass结构如下:
importorg.eclipse.jetty.server.Request; classSerializer_XXX{ //Request中每个字段对应一个xxx_asm_field_type字段 Typecontext_asm_field_type; TypeuserIdentityScope_asm_field_type; //其他字段省略... //构造函数 Serializer_XXX(){ context_asm_field_type=getFieldType(Request.class,"context"); userIdentityScope_asm_field_type=getFieldType(Request.class,"userIdentityScope"); //其他字段忽略... } }
构造函数会执行失败,原因是找不到org.eclipse.jetty.server.Request。
为什么找不到类?
问题涉及到的3个classloader,它们的结构如下:
ServerClassLoader -> WebAppClassLoader -> AsmClassLoader
Request的classloader是ServerClassLoader,Serializer_XXX的classloader是AsmClassLoader。
按照类加载的约定,加载Request类的流程如下:
理论上Request是可以成功加载的。问题出在WebAppClassLoader,它违反了类加载的约定,没有让parent加载,而是自己直接加载,结果找不到,就直接抛ClassNotFoundException。
WebAppClassLoader的行为是可配置的,如果启动参数org.eclipse.jetty.server.webapp.parentLoaderPriority是true,它就会先让parent找,否则就自己找,默认是false
fastjson的问题在哪里?
fastjson实际是有考虑类加载的问题的,它是判断Request的ClassLoader是不是AsmClassLoader或AsmClassLoader的祖先。但是由于WebAppClassLoader违背了类加载的约定,fastjson的判断就变得不可靠了。