当前位置: 首页 > 后端技术 > Java

波折!记一次非堆内存泄漏(CXF+Jackson)排查-Part2

时间:2023-04-02 09:12:44 Java

接上文:一波三折!记得non-heapmemoryleak(CXF+Jackson)的排查问题还在,找不到头绪。当我们以为问题已经解决,补丁更新到客户的生产环境时,意外发生了。系统还是老样子,非堆内存还是稳定的。增加。查看jvm中class的基本信息:[root@iZ6h4s886i9imdZ~]#jstat-class1401519LoadedBytesUnloadedBytesTime634714678273.22055322886.8825.14由于客户的生产环境不允许随机操作,class的个数还是增加到了630000个(无法调试),另外,我也担心内存过大,dump掉整个内存会导致系统宕机,所以暂时放弃了抓内存分析的想法。是不是classloader链的修改没有效果?哪些对象、类或类加载器保存在外部?由于CXF2.7.3中有明显的classloader链,首先想到的就是抓取所有的classloader以及它们之间的父子关系,看是否形成了classloader链。尝试分析classloader关系捕捉所有classloader对象的关系,这里需要借用JVMTI中的java.lang.instrument.Instrumentation对象,需要在运行时使用javaagent技术附加外部代码到运行的jvm中runtime,根据Instrumentation上可用的所有加载类对象收集类加载器信息。参考:分析jvm内存中的classloader关系以下为部分爬取内容:com.toone.v3.platform-34vbase-07aap-01service-orchestration[233]:COUNT:95com.toone.v3.platform-34vbase-07aap-02service-orchestration-kafka[234]:计数:1230java.net.URLClassLoader@1066f0c:计数:236URLS:-文件:/tmp/com.toone.itop.vbase.ws.impl.apiserver.cxfRewrite。VWSDynamicClientFactory@3d23dba2-1668562006596-classes/java.net.URLClassLoader@1092bb1c:计数:236个URL:-文件:/tmp/com.toone.itop.vbase.ws.impl.apiserver.cxfRewrite.VWSDynamicClientFactory@7a140ffb-16659548-classes/爪哇。net.URLClassLoader@10ac4881:COUNT:236URLS:-file:/tmp/com.toone.itop.vbase.ws.impl.apiserver.cxfRewrite.VWSDynamicClientFactory@75865fbe-1668561473152-classes/#Morejava.net.URLClassLoader省略了它从上面的数据可以看出,CXF生成的ClassLoader并没有形成父依赖链,但是这些ClassLoader是无法卸载的。哪里有问题?由于客户现场环境问题,无法在模拟环境中重现。我们的想法不得不回到是否能够模拟与客户环境尽可能一致的操作。因为原来模拟调用的webservice是单独开发的,返回值只是一个简单的String,所以考虑调用第三方的webservice来测试。上网搜索,找到这个天气预报的publicwebservice来模拟调用。结果一下子就再现了漏课的场景。接下来,获取内存转储数据:jmap-dump:format=b,file=heapdump.phrofpid使用jprofiler打开捕获的内存快照。步骤如下:找到heapwalker->当前对象集->选择“Inspect”->选择“Classes&ClassLoaders”->同名类->在新对象集中显示计算结果->在对话框中选择Reference->Incoming引用(Incoming),执行看效果:在jprofiler中找到webservice生成的class(一般特征比较明显,比如上面天气预报的className:cn.com.webxml.*)展开参考一下,我们会发现这些课都是jackson开的。再次查看代码,再次查看客户端代码,发现使用CXFDynamicClientFactory动态调用webservice后,返回值使用jackson转换为json对象。至此,类泄漏的原因就很清楚了。猜猜看,我们也可以想象,jackson在将对象转为json的时候,需要解析类的属性。一般情况下,为了避免重复解析同一个类,必然会将解析结果缓存起来进行备份。在正常的系统中,类的数量一般是稳定的,不会出现大量一次性使用的类,所以这种缓存策略是合理的。但是CXF一起动态调用webservice+jackson时,会出现classleakage。解决方案弄清楚了问题的原因,解决方案就很容易确定了,比如:不用jackson,自己重新实现一个反射的tojson方法并不复杂。查看jackson文档看是否有清理缓存类的开关,或者方法jackson从全局工具类中,创建、释放或者缓存每次动态调用webservice时生成的类/类加载器,以便复用,一是解决了类泄露的问题,二是因为减少了每次解析wsdl文件和java代码生成、java代码编译、类加载的过程,对于动态调用webservices可能会有一些性能提升。当然,为了防止缓存因为wsdl的变化而变脏,可以通过wsdl的MD5来判断缓存的有效性。由于不确定CXF的ClientImpl对象是否是重对象,所以不确定这个对象是否适合缓存和并发情况下使用,改造方案的主要缓存内容是ClassLoader;具体实现见下一篇。