這里就涉及到如何回收Perm區(qū)咳榜、或者Metaspace中已經加載的類了酥诽,如果一個類的類加載器對象沒有GC Root關聯(lián),那么可以通過FGC的方式回收這些類琼懊。不過阁簸,如果通過JVM內部的類加載器比如AppClassLoader去加載這些類的話,可能永遠也不能回收了肩碟,所以得通過自定義的類加載器去實現(xiàn)Agent類的加載動作强窖,因為自定義的類加載器對象,我們可以自己控制削祈。
下面是自定義類加載器的實現(xiàn)
publicclassAgentClassLoaderextendsURLClassLoader{? ? publicAgentClassLoader(URL[] urls) {super(urls,ClassLoader.getSystemClassLoader().getParent());? ? }@OverrideprotectedClass loadClass(Stringname, boolean resolve)throwsClassNotFoundException{finalClass loadedClass = findLoadedClass(name);if(loadedClass !=null) {if(resolve) {? ? ? ? ? ? ? ? resolveClass(loadedClass);? ? ? ? ? ? }returnloadedClass;? ? ? ? }// 優(yōu)先從parent(SystemClassLoader)里加載系統(tǒng)類翅溺,避免拋出ClassNotFoundExceptionif(name !=null&& (name.startsWith("sun.") || name.startsWith("java."))) {returnsuper.loadClass(name, resolve);? ? ? ? }// 先從agent中加載try{Class aClass = findClass(name);if(resolve) {? ? ? ? ? ? ? ? resolveClass(aClass);? ? ? ? ? ? }returnaClass;? ? ? ? }catch(Exceptione) {// ignore}returnsuper.loadClass(name, resolve);? ? }}
這樣,通過AgentClassLoader加載的類髓抑,就可以和業(yè)務的類完全隔離開咙崎,在需要回收這些類的時候,只要把AgentClassLoader對象和GC root的關聯(lián)完全掐斷就行吨拍。
不過用了AgentClassLoader之后褪猛,還是遇到了一些坑,比如在Agent中使用Cat的時候羹饰,因為Cat是單例模式伊滋,都是通過?Cat.logEvent?這種方式使用,所以在第一次使用Cat的時候队秩,Cat內部會進行初始化笑旺,比如系統(tǒng)信息上報邏輯。因為業(yè)務邏輯在使用Cat的時候馍资,已經初始化過了一次筒主,在Agent內部使用時,因為是通過AgentClassLoader加載的鸟蟹,又是一個全新的Cat乌妙,相當于那些上報邏輯又初始化了一次,這這種明顯是不行的建钥,那如何在Agent中可以使用業(yè)務加載的那個Cat對象呢藤韵?
后來想到了一個解決方案,通過一個CatAdapt封裝了一下Cat
publicclassCatAdapter {privatestaticfinalLogger logger = LoggerFactory.getLogger(CatAdapter.class);privatestaticMethod logEvent;publicstaticvoidinit(ClassLoader classLoader) {try{ClasscatClazz =Class.forName("com.dianping.cat.Cat",true, classLoader);? ? ? ? ? ? logEvent = catClazz.getMethod("logEvent", String.class, String.class);? ? ? ? }catch(Exception e) {? ? ? ? ? ? logger.error("cat adapter init failed", e);? ? ? ? }? ? }publicstaticvoidlogEvent(String type, String name) {if(logEvent !=null) {try{? ? ? ? ? ? ? ? logEvent.invoke(null, type, name);? ? ? ? ? ? }catch(Exception e) {// ignore}? ? ? ? }? ? }}
在Agent初始化入口的agentmain方法中锦针,獲取當前線程的classLoader
ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader();ClasscatAdapter = agentLoader.loadClass("com.**.**.CatAdapter");Method catAdapterInit = catAdapter.getMethod("init", ClassLoader.class);catAdapterInit.invoke(null, currentClassLoader);
又通過agentLoader去加載CatAdapter類荠察,在init方法中置蜀,通過當前線程的classLoader去加載真正的Cat類,這時拿到的Cat的class對象和業(yè)務的Cat class對象是同一個悉盆,從而避免了上述問題盯荤,在Agent內部就可以通過CatAdapter實現(xiàn)Cat方法的代理調用,實現(xiàn)數(shù)據的埋點焕盟。
卸載時的一些坑
為了驗證執(zhí)行FGC時秋秤,是否可以把無用的類回收,遇到了下面這些坑脚翘。
1灼卢、很單純的以為把agentLoader設置為null,我就可以快樂的回收了来农,執(zhí)行了?jmap -histo:live pid?之后鞋真,驚喜的發(fā)現(xiàn),Agent的類還在沃于。
2涩咖、為了看下為什么沒有回收,把堆對象dump下來繁莹,通過mat工具進行分析檩互,找了一個Agent的類,發(fā)現(xiàn)其對象正被agentLoader對象拽著咨演,順騰摸瓜闸昨,發(fā)現(xiàn)agentLoader被線程池的線程拽著,這下明白了薄风,需要把這些線程池給shutdown掉
3饵较、因為在Agent初始化的時候,創(chuàng)建了幾個線程池處理一些內部邏輯遭赂,所以要卸載Agent的時候告抄,這些線程池必須shutdown。
4嵌牺、把線程池shutdown之后,繼續(xù)使用?jmap -histo:live pid?龄糊,發(fā)現(xiàn)這些類特么還在逆粹,真是頑固啊。dump下來炫惩,繼續(xù)分析僻弹,發(fā)現(xiàn)agentLoader還被一個?Finalizer?對象給勾著!這是為啥他嚷,為什么有Finalizer對象勾著它蹋绽?按照我的理解芭毙,只有重寫了finalize方法的類才會有Finalizer對象,一瞬間卸耘,我懷疑是不是線程池的類重寫了finalize方法退敦,一查還真是,在?ThreadPoolExecutor?類中重寫了finalize方法蚣抗。
5侈百、重寫了finalize方法,這種情況理論上要經過兩次GC才會被回收翰铡,執(zhí)行了兩次?jmap -histo:live pid?钝域,Agent的類果然沒了!6А例证!那個開心。
6迷捧、后面又一次不經意的發(fā)現(xiàn)又無法回收了织咧,又只能dump下來,繼續(xù)分析党涕,這次agentLoader對象被業(yè)務線程的threadLocal對象給拽著了烦感,死都不放手。
這一次真的查了好久膛堤,因為不好復現(xiàn)手趣,前前后后驗證了多次,發(fā)現(xiàn)在使用了Agent的Mock功能之后肥荔,就會出現(xiàn)這個問題绿渣,Mock功能會根據業(yè)務配置的String字符串,通過jackson框架反序列化成一個對象并返回燕耿。
jackson在序列化的時候中符,需要開辟一塊內存空間,為了能夠重復利用這塊空間誉帅,jackson默認把這個內存空間封裝成一個SoftReference保存在ThreadLocal中淀散。
這樣每個線程都有一塊內存可以重復使用,這原本是好事蚜锨,但是在我們這档插,變成了一只暗搓搓的手,死死抓著agentLoader不放亚再,導致了所有類都不能回收郭膛。
JsonFactory f =newJsonFactory();f.disable(JsonFactory.Feature.USE_THREAD_LOCAL_FOR_BUFFER_RECYCLING);
最終通過取消這個特性,每次序列化都去創(chuàng)建一塊內存氛悬,這樣就可以避免這個問題则剃,又可以快樂的回收了耘柱。
在此我向大家推薦一個架構學習交流群。交流學習群號:938837867 暗號:555 里面會分享一些資深架構師錄制的視頻錄像:有Spring棍现,MyBatis调煎,Netty源碼分析,高并發(fā)轴咱、高性能汛蝙、分布式、微服務架構的原理朴肺,JVM性能優(yōu)化窖剑、分布式架構等這些成為架構師必備