日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > java >内容正文

java

Java Agent的隔离实现以及卸载时一些坑

發(fā)布時間:2023/12/3 java 92 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java Agent的隔离实现以及卸载时一些坑 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

轉(zhuǎn)載自? ?Java Agent的隔離實現(xiàn)以及卸載時一些坑

在《一文帶你了解Java Agent》中,讓大家了解了Java Agent的來龍去脈,當(dāng)通過attach方式去動態(tài)加載一個Java Agent時,Agent中的類會被加載到業(yè)務(wù)的虛擬機(jī)中,在使用完Agent的之后,如果想卸載這些無用的類,怎么實現(xiàn)?

這里就涉及到如何回收Perm區(qū)、或者M(jìn)etaspace中已經(jīng)加載的類了,如果一個類的類加載器對象沒有GC Root關(guān)聯(lián),那么可以通過FGC的方式回收這些類。不過,如果通過JVM內(nèi)部的類加載器比如AppClassLoader去加載這些類的話,可能永遠(yuǎn)也不能回收了,所以得通過自定義的類加載器去實現(xiàn)Agent類的加載動作,因為自定義的類加載器對象,我們可以自己控制。

下面是自定義類加載器的實現(xiàn)

public class AgentClassLoader extends URLClassLoader {public AgentClassLoader(URL[] urls) {super(urls, ClassLoader.getSystemClassLoader().getParent());}@Overrideprotected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {final Class<?> loadedClass = findLoadedClass(name);if (loadedClass != null) {if (resolve) {resolveClass(loadedClass);}return loadedClass;}// 優(yōu)先從parent(SystemClassLoader)里加載系統(tǒng)類,避免拋出ClassNotFoundExceptionif (name != null && (name.startsWith("sun.") || name.startsWith("java."))) {return super.loadClass(name, resolve);}// 先從agent中加載try {Class<?> aClass = findClass(name);if (resolve) {resolveClass(aClass);}return aClass;} catch (Exception e) {// ignore}return super.loadClass(name, resolve);} }

這樣,通過AgentClassLoader加載的類,就可以和業(yè)務(wù)的類完全隔離開,在需要回收這些類的時候,只要把AgentClassLoader對象和GC root的關(guān)聯(lián)完全掐斷就行。

不過用了AgentClassLoader之后,還是遇到了一些坑,比如在Agent中使用Cat的時候,因為Cat是單例模式,都是通過 Cat.logEvent這種方式使用,所以在第一次使用Cat的時候,Cat內(nèi)部會進(jìn)行初始化,比如系統(tǒng)信息上報邏輯。因為業(yè)務(wù)邏輯在使用Cat的時候,已經(jīng)初始化過了一次,在Agent內(nèi)部使用時,因為是通過AgentClassLoader加載的,又是一個全新的Cat,相當(dāng)于那些上報邏輯又初始化了一次,這這種明顯是不行的,那如何在Agent中可以使用業(yè)務(wù)加載的那個Cat對象呢?

后來想到了一個解決方案,通過一個CatAdapt封裝了一下Cat

public class CatAdapter {private static final Logger logger = LoggerFactory.getLogger(CatAdapter.class);private static Method logEvent;public static void init(ClassLoader classLoader) {try {Class catClazz = 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);}}public static void logEvent(String type, String name) {if (logEvent != null) {try {logEvent.invoke(null, type, name);} catch (Exception e) {// ignore}}} }

在Agent初始化入口的agentmain方法中,獲取當(dāng)前線程的classLoader

ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader(); Class catAdapter = agentLoader.loadClass("com.**.**.CatAdapter"); Method catAdapterInit = catAdapter.getMethod("init", ClassLoader.class); catAdapterInit.invoke(null, currentClassLoader);

又通過agentLoader去加載CatAdapter類,在init方法中,通過當(dāng)前線程的classLoader去加載真正的Cat類,這時拿到的Cat的class對象和業(yè)務(wù)的Cat class對象是同一個,從而避免了上述問題,在Agent內(nèi)部就可以通過CatAdapter實現(xiàn)Cat方法的代理調(diào)用,從而實現(xiàn)數(shù)據(jù)的埋點。

卸載時的一些坑

為了驗證執(zhí)行FGC時,是否可以把無用的類回收,遇到了下面這些坑。 1、很單純的以為把a(bǔ)gentLoader設(shè)置為null,我就可以快樂的回收了,執(zhí)行了 jmap-histo:live pid之后,驚喜的發(fā)現(xiàn),Agent的類還在。 2、為了看下為什么沒有回收,把堆對象dump下來,通過mat工具進(jìn)行分析,找了一個Agent的類,發(fā)現(xiàn)其對象正被agentLoader對象拽著,順騰摸瓜,發(fā)現(xiàn)agentLoader被線程池的線程拽著,這下明白了,需要把這些線程池給shutdown掉 3、因為在Agent初始化的時候,創(chuàng)建了幾個線程池處理一些內(nèi)部邏輯,所以要卸載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方法,這種情況理論上要經(jīng)過兩次GC才會被回收,執(zhí)行了兩次 jmap-histo:live pid,Agent的類果然沒了!!!那個開心。 6、后面又一次不經(jīng)意的發(fā)現(xiàn)又無法回收了,又只能dump下來,繼續(xù)分析,這次agentLoader對象被業(yè)務(wù)線程的threadLocal對象給拽著了,死都不放手。

這一次真的查了好久,因為不好復(fù)現(xiàn),前前后后驗證了多次,最終發(fā)現(xiàn)在使用了Agent的Mock功能之后,就會出現(xiàn)這個問題,Mock功能會根據(jù)業(yè)務(wù)配置的String字符串,通過jackson框架反序列化成一個對象并返回。

jackson在序列化的時候,需要開辟一塊內(nèi)存空間,為了能夠重復(fù)利用這塊空間,jackson默認(rèn)把這個內(nèi)存空間封裝成一個SoftReference保存在ThreadLocal中。

?

這樣每個線程都有一塊內(nèi)存可以重復(fù)使用,這原本是好事,但是在我們這,變成了一只暗搓搓的手,死死抓著agentLoader不放,導(dǎo)致了所有類都不能回收。

JsonFactory f = new JsonFactory();f.disable(JsonFactory.Feature.USE_THREAD_LOCAL_FOR_BUFFER_RECYCLING);

最終取消這個特性,每次序列化都去創(chuàng)建一塊內(nèi)存,這樣就可以避免這個問題,又可以快樂的回收了。

后面還有更多的坑等著去填,越填越開心...

總結(jié)

以上是生活随笔為你收集整理的Java Agent的隔离实现以及卸载时一些坑的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。