成人午夜视频全免费观看高清-秋霞福利视频一区二区三区-国产精品久久久久电影小说-亚洲不卡区三一区三区一区

實(shí)現(xiàn)Java探針中遇到的問題有哪些

這篇文章主要講解了“實(shí)現(xiàn)Java探針中遇到的問題有哪些”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“實(shí)現(xiàn)Java探針中遇到的問題有哪些”吧!

陽春網(wǎng)站制作公司哪家好,找成都創(chuàng)新互聯(lián)公司!從網(wǎng)頁設(shè)計(jì)、網(wǎng)站建設(shè)、微信開發(fā)、APP開發(fā)、成都響應(yīng)式網(wǎng)站建設(shè)等網(wǎng)站項(xiàng)目制作,到程序開發(fā),運(yùn)營(yíng)維護(hù)。成都創(chuàng)新互聯(lián)公司自2013年創(chuàng)立以來到現(xiàn)在10年的時(shí)間,我們擁有了豐富的建站經(jīng)驗(yàn)和運(yùn)維經(jīng)驗(yàn),來保證我們的工作的順利進(jìn)行。專注于網(wǎng)站建設(shè)就選成都創(chuàng)新互聯(lián)公司

Java探針可以在Java應(yīng)用運(yùn)行時(shí)毫無感知的切入應(yīng)用代碼,是一種用于監(jiān)聽代碼行為或改變代碼行為的工具。

分布式調(diào)用鏈路追蹤的實(shí)現(xiàn)無非兩種方式,代碼侵入式和非代碼侵入式,基于Java探針實(shí)現(xiàn)的屬于非代碼侵入式。

運(yùn)行在Java虛擬機(jī)上的編程語言所編寫的代碼,都有一種統(tǒng)一的中間格式:class文件格式。實(shí)現(xiàn)動(dòng)態(tài)修改class字節(jié)碼插入額外行為的代碼,可實(shí)現(xiàn)非代碼侵入式的應(yīng)用調(diào)用行為收集。

得益于Java SE 6提供的Instrumentation接口?;贗nstrumentation可開發(fā)運(yùn)行時(shí)修改class字節(jié)碼的Java Agent應(yīng)用(Java探針),可在類加載之前替換類的字節(jié)碼、或在類加載之后通過重新加載類方式修改類的字節(jié)碼。

只是實(shí)現(xiàn)運(yùn)行時(shí)修改class字節(jié)碼還不足以稱為“探針”?;贗nstrumentation開發(fā)的Java Agent,只需要在Java應(yīng)用啟動(dòng)命令上加上虛擬機(jī)參數(shù)“-javaagent”指定Java Agent應(yīng)用jar包的位置,而不需要在工程項(xiàng)目中引入其jar包,即可將探針插入應(yīng)用代碼的各個(gè)角落。通過與應(yīng)用使用不同的類加載實(shí)現(xiàn)環(huán)境隔離,讓人有種Java Agent是吸附在應(yīng)用上運(yùn)行的錯(cuò)覺。

Instrumentation之所以難駕馭,在于需要了解Java類加載機(jī)制以及字節(jié)碼,一不小心就能遇到各種陌生的Exception。筆者在實(shí)現(xiàn)Java探針時(shí)就踩過不少坑,其中一類就是類加載相關(guān)的問題,也是本篇所要跟大家分享的。

實(shí)現(xiàn)Java探針中遇到的問題有哪些

 

父類加載器加載的類不能引用子類加載器加載的類

由父類加載器加載的類,不能引用子類加載器加載的類,否則會(huì)拋出NoClassDefFoundError。

怎么理解這句話呢?這其實(shí)也是道面試題。

JDK提供的java.*類都由啟動(dòng)類加載器加載。如果我們?cè)趈ava agent中修改java包下的類,插入調(diào)用logback打印日記的代碼,結(jié)果會(huì)怎樣?由于java agent包下的logback由AppClassLoader(應(yīng)用類加載器,也稱為系統(tǒng)類加載器)加載,而加載java包下的類的啟動(dòng)類加載器是AppClassLoader的父類加載器,在java包下的類中插入調(diào)用logback打印日記的代碼,首先在加載java包下的類時(shí),jvm會(huì)查看啟動(dòng)類加載器有沒有加載過這個(gè)類,如果沒有加載過嘗試加載,但啟動(dòng)類加載器加載不了logback包的類,而啟動(dòng)類加載器不會(huì)向子類加載器去詢問,任何類加載器都不會(huì)向子類加載器詢問子類加載器是否能加載,即使子類加載器加載了這個(gè)類。所以就會(huì)出現(xiàn)NoClassDefFoundError。

如果非要修改java包下的類,且非要在java包下的類中訪問項(xiàng)目中我們編寫的類或者第三方j(luò)ar包提供的類、或者我們編寫的javaagent包下的類,如何避免NoClassDefFoundError呢?

筆者遇到這個(gè)問題網(wǎng)上找過很多資源,遺憾的是并未找到。于是筆者想起自己電腦上下載有Arthas的源碼,不如學(xué)習(xí)下Arthas是如何解決的。

Arthas是Alibaba開源的一款Java診斷工具,非常適合用于線上問題排查。

參考Alibaba開源的Arthas的解決方案:

用于接收埋點(diǎn)代碼上報(bào)事件的類(Spy):

public final class Spy {
   public static void before(String className, String methodName, String descriptor, Object[] params) {
   }

   public static void complete(Object returnValueOrThrowable, String className, String methodName, String descriptor) {
   }
}
 
  • before:方法執(zhí)行之前上報(bào);

  • complete:方法return之前或者拋出異常之前上報(bào),當(dāng)方法拋出異常時(shí),第一個(gè)參數(shù)為異常,否則第一個(gè)參數(shù)為返回值;

將Spy放在一個(gè)獨(dú)立的jar包下,在premain、agentmain方法中調(diào)用Instrumentation的appendToBootstrapClassLoaderSearch方法,將Spy類所在的jar包交由啟動(dòng)類加載器掃描加載,如下代碼所示。

// agent-spy.jar
String agentSpyJar = jarPath[1];
File spyJarFile = new File(agentSpyJar);
instrumentation.appendToBootstrapClassLoaderSearch(new JarFile(spyJarFile));
    

在Spy類中打印類加載器,如果打印的結(jié)果為null,則說明Spy類是由啟動(dòng)類加載器加載的。

public final class Spy {
   static {
       System.out.println("Spy class loader is " + Spy.class.getClassLoader());
   }
   //.......
}
 

最后,給Spy注入上報(bào)方法,在Spy中通過反射調(diào)用上報(bào)方法,完整的Spy類的代碼如下。

public final class Spy {

   public static Method beforMethod;
   public static Method completeMethod;

   public static void before(String className, String methodName, String descriptor, Object[] params) {
       if (beforMethod != null) {
           try {
               beforMethod.invoke(null, className, methodName, descriptor, params);
           } catch (IllegalAccessException | InvocationTargetException e) {
           }
       }
   }

   public static void complete(Object returnValueOrThrowable, String className, String methodName, String descriptor) {
       if (completeMethod != null) {
           try {
               completeMethod.invoke(null, returnValueOrThrowable, className, methodName, descriptor);
           } catch (IllegalAccessException | InvocationTargetException e) {
           }
       }
   }
}

通過反射調(diào)用對(duì)性能會(huì)有所影響,特別是調(diào)用鏈路上每個(gè)方法都需要反射調(diào)用兩個(gè)上報(bào)方法。

可能不完全理解正確,但筆者試過這個(gè)方案確實(shí)可行。

實(shí)現(xiàn)Agent與應(yīng)用環(huán)境隔離

為什么要實(shí)現(xiàn)隔離?

隔離是避免Agent污染應(yīng)用自身,使開發(fā)Java Agent無需考慮引入的jar包是否與目標(biāo)應(yīng)用引入的jar包沖突。

Java Agent與Spring Boot應(yīng)用相遇時(shí)會(huì)發(fā)生什么?

Spring Boot應(yīng)用打包后,將Agent附著到應(yīng)用啟動(dòng)可能會(huì)拋出醒目的NoClassDefFoundError異常,這在IDEA中測(cè)試是不會(huì)發(fā)生的,而背后的原因是Agent與打包后的Spring Boot應(yīng)用使用了不同的類加載器。

我們可能會(huì)在Agent中調(diào)用被監(jiān)控的SpringBoot應(yīng)用的代碼,也可能調(diào)用Agent依賴的第三方j(luò)ar包的API,而這些jar包恰好在SpringBoot應(yīng)用中也有導(dǎo)入,就可能會(huì)出現(xiàn)NoClassDefFoundError。

Agent的jar包由AppClassLoader類加載器(系統(tǒng)類加載器)所加載。

在IDEA中,項(xiàng)目的class文件和第三方庫是通過AppClassLoader加載的,而使用-javaagent指定的jar也是通過AppClassLoader加載,所以在idea中測(cè)試不會(huì)遇到這個(gè)問題。

SpringBoot應(yīng)用打包后,JVM進(jìn)程啟動(dòng)入口不再是我們寫的main方法,而是SpringBoot生成的啟動(dòng)類。SpringBoot使用自定義的類加載器(LaunchedClassLoader)加載jar中的類和第三方j(luò)ar包中的類,該類加載器的父類加載器為AppClassLoader。

也就是說,SpringBoot應(yīng)用打包后,加載javaagent包下的類使用的類加載器是SpringBoot使用的類加載器的父類加載器。

如何實(shí)現(xiàn)隔離?


讓加載agent包不使用AppClassLoader加載器加載,而是使用自定義的類加載器加載。

參考Alibaba開源的Arthas的實(shí)現(xiàn),自定義URLClassLoader加載agent包以及agent依賴的第三方j(luò)ar包。

由于premain或者agentmain方法所在的類由jvm使用AppClassLoader所加載,所以必須將agent拆分為兩個(gè)jar包。核心功能放在agent-core包下,premain或者agentmain方法所在的類放在agent-boot包下。在premain或者agentmain方法中使用自定義的URLClassLoader類加載器加載agent-core。

第一步:

自定義類加載器OnionClassLoader,繼承URLClassLoader,如下代碼所示:

public class OnionClassLoader extends URLClassLoader {

   public OnionClassLoader(URL[] urls) {
       super(urls, ClassLoader.getSystemClassLoader().getParent());
   }

   @Override
   protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
       final Class<?> loadedClass = findLoadedClass(name);
       if (loadedClass != null) {
           return loadedClass;
       }
       // 優(yōu)先從parent(SystemClassLoader)里加載系統(tǒng)類,避免拋出ClassNotFoundException
       if (name != null && (name.startsWith("sun.") || name.startsWith("java."))) {
           return super.loadClass(name, resolve);
       }
       try {
           Class<?> aClass = findClass(name);
           if (resolve) {
               resolveClass(aClass);
           }
           return aClass;
       } catch (Exception e) {
           // ignore
       }
       return super.loadClass(name, resolve);
   }

}
    

同時(shí)在構(gòu)造方法中指定OnionClassLoader的父類加載器為AppClassLoader的父類加載器。

ClassLoader.getSystemClassLoader():獲取系統(tǒng)類加載器(AppClassLoader)

第二步:

在premain或者agentmain方法中使用OnionClassLoader類加載器加載agent-core。

// 1
File agentJarFile = new File(agentJar);
final ClassLoader agentLoader = new OnionClassLoader(new URL[]{agentJarFile.toURI().toURL()});
// 2
Class<?> transFormer = agentLoader.loadClass("com.msyc.agent.core.OnionClassFileTransformer");
// 3
Constructor<?> constructor = transFormer.getConstructor(String.class);
Object instance = constructor.newInstance(opsParams);
// 4
instrumentation.addTransformer((ClassFileTransformer) instance);
    
  • 1、根據(jù)agent-core.jar所在絕對(duì)路徑構(gòu)造OnionClassLoader;

  • 2、加載agent-core.jar下的ClassFileTransformer;

  • 3、使用反射創(chuàng)建ClassFileTransformer實(shí)例;

  • 4、將ClassFileTransformer添加到Instrumentation;

OnionClassFileTransformer類所依賴的agent-core包下的類,自然也會(huì)被使用OnionClassLoader類加載器加載,包括agent-core依賴的第三方j(luò)ar包。

實(shí)現(xiàn)Java探針中遇到的問題有哪些

適配webmvc框架

生成分布式調(diào)用鏈日記的難點(diǎn)在于方法埋點(diǎn)和方法調(diào)用日記串連。

分布式調(diào)用鏈日記串連的方式有多種,筆者采用的是最簡(jiǎn)單的方式:打點(diǎn)id+打點(diǎn)時(shí)間。

對(duì)于同進(jìn)程內(nèi)的同線程,可用打點(diǎn)id將調(diào)用的方法串連起來,根據(jù)打點(diǎn)時(shí)間與一個(gè)累加器的值排序方法調(diào)用日記。

對(duì)于不同進(jìn)程,通過傳遞打點(diǎn)id可將不同應(yīng)用的打點(diǎn)日記串連起來,根據(jù)打點(diǎn)時(shí)間排序。

例如,適配webmvc框架的目的是從請(qǐng)求頭獲取調(diào)用來源傳遞過來的打點(diǎn)ID(事務(wù)ID)。對(duì)DispatcherServlet#doDispatch方法插樁,從HttpServletRequest參數(shù)獲取請(qǐng)求頭“S-Tid”?!癝-Tid”是自定義的請(qǐng)求頭參數(shù),用于傳遞打點(diǎn)ID。

筆者在實(shí)現(xiàn)適配webmvc和openfeign時(shí)都遇到了同樣的問題,如在適配webmvc時(shí),修改DispatcherServlet的doDispatch方法時(shí),asm框架拋出java.lang.TypeNotPresentException。

  • java.lang.TypeNotPresentException:當(dāng)應(yīng)用程序試圖使用表示類型名稱的字符串對(duì)類型進(jìn)行訪問,但無法找到帶有指定名稱的類型定義時(shí),拋出該異常。

其原因是,使用asm框架改寫DispatcherServlet類時(shí),asm會(huì)使用Class.forName方法加載符號(hào)引用的類,如果加載不到目標(biāo)類則拋出TypeNotPresentException。

默認(rèn)asm會(huì)使用加載自身的類加載器去嘗試加載當(dāng)前改寫類所依賴的一些類,而加載asm框架使用的類加載器與加載agent-core包使用的是同一個(gè)類加載器,DispatcherServlet則由SpringBoot的LaunchedClassLoader類加載器所加載。

好在ClassFileTransformer#transform方法傳遞了用于加載當(dāng)前類的類加載器:

public class OnionClassFileTransformer implements ClassFileTransformer {
   @Override
   public byte[] transform(ClassLoader loader, String className,
                           Class<?> classBeingRedefined,
                           ProtectionDomain protectionDomain,
                           byte[] classfileBuffer) {
            // ......
   }
}
  • 如果當(dāng)前需要改寫的類是DispatcherServlet,則transform方法的第一個(gè)參數(shù)為即將用于加載DispatcherServlet類的類加載器;

我們只需要指定asm使用ClassFileTransformer#transform方法傳遞進(jìn)來的類加載器加載DispatcherServlet依賴的類即可。

ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES) {
      @Override
      protected ClassLoader getClassLoader() {
           return loader;
      }
};

如代碼所示,我們重寫asm的ClassWriter類的getClassLoader方法,返回的類加載器是ClassFileTransformer#transform方法傳遞進(jìn)來的類加載器。

感謝各位的閱讀,以上就是“實(shí)現(xiàn)Java探針中遇到的問題有哪些”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對(duì)實(shí)現(xiàn)Java探針中遇到的問題有哪些這一問題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是創(chuàng)新互聯(lián),小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!

文章標(biāo)題:實(shí)現(xiàn)Java探針中遇到的問題有哪些
鏈接URL:http://jinyejixie.com/article34/pggcse.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供搜索引擎優(yōu)化、虛擬主機(jī)網(wǎng)頁設(shè)計(jì)公司、全網(wǎng)營(yíng)銷推廣、服務(wù)器托管定制開發(fā)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

h5響應(yīng)式網(wǎng)站建設(shè)
宜兰市| 临江市| 大渡口区| 曲麻莱县| 临猗县| 驻马店市| 鸡泽县| 静宁县| 公主岭市| 信丰县| 海林市| 泰兴市| 洪湖市| 扎鲁特旗| 淮北市| 澜沧| 揭西县| 南乐县| 陕西省| 宁安市| 莱芜市| 会宁县| 金坛市| 潮州市| 大关县| 本溪| 林口县| 高唐县| 隆德县| 肃北| 台安县| 靖江市| 孝昌县| 湖北省| 弋阳县| 苏州市| 辉南县| 甘谷县| 加查县| 鄂州市| 上蔡县|