亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

實現Java探針中遇到的問題有哪些

發布時間:2021-11-24 16:52:44 來源:億速云 閱讀:116 作者:iii 欄目:大數據

這篇文章主要講解了“實現Java探針中遇到的問題有哪些”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“實現Java探針中遇到的問題有哪些”吧!

Java探針可以在Java應用運行時毫無感知的切入應用代碼,是一種用于監聽代碼行為或改變代碼行為的工具。


分布式調用鏈路追蹤的實現無非兩種方式,代碼侵入式和非代碼侵入式,基于Java探針實現的屬于非代碼侵入式。


運行在Java虛擬機上的編程語言所編寫的代碼,都有一種統一的中間格式:class文件格式。實現動態修改class字節碼插入額外行為的代碼,可實現非代碼侵入式的應用調用行為收集。


得益于Java SE 6提供的Instrumentation接口。基于Instrumentation可開發運行時修改class字節碼的Java Agent應用(Java探針),可在類加載之前替換類的字節碼、或在類加載之后通過重新加載類方式修改類的字節碼。


只是實現運行時修改class字節碼還不足以稱為“探針”。基于Instrumentation開發的Java Agent,只需要在Java應用啟動命令上加上虛擬機參數“-javaagent”指定Java Agent應用jar包的位置,而不需要在工程項目中引入其jar包,即可將探針插入應用代碼的各個角落。通過與應用使用不同的類加載實現環境隔離,讓人有種Java Agent是吸附在應用上運行的錯覺。


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


實現Java探針中遇到的問題有哪些


 

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

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


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


JDK提供的java.*類都由啟動類加載器加載。如果我們在java agent中修改java包下的類,插入調用logback打印日記的代碼,結果會怎樣?由于java agent包下的logback由AppClassLoader(應用類加載器,也稱為系統類加載器)加載,而加載java包下的類的啟動類加載器是AppClassLoader的父類加載器,在java包下的類中插入調用logback打印日記的代碼,首先在加載java包下的類時,jvm會查看啟動類加載器有沒有加載過這個類,如果沒有加載過嘗試加載,但啟動類加載器加載不了logback包的類,而啟動類加載器不會向子類加載器去詢問,任何類加載器都不會向子類加載器詢問子類加載器是否能加載,即使子類加載器加載了這個類。所以就會出現NoClassDefFoundError。


如果非要修改java包下的類,且非要在java包下的類中訪問項目中我們編寫的類或者第三方jar包提供的類、或者我們編寫的javaagent包下的類,如何避免NoClassDefFoundError呢?


筆者遇到這個問題網上找過很多資源,遺憾的是并未找到。于是筆者想起自己電腦上下載有Arthas的源碼,不如學習下Arthas是如何解決的。


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


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


用于接收埋點代碼上報事件的類(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:方法執行之前上報;

  • complete:方法return之前或者拋出異常之前上報,當方法拋出異常時,第一個參數為異常,否則第一個參數為返回值;


將Spy放在一個獨立的jar包下,在premain、agentmain方法中調用Instrumentation的appendToBootstrapClassLoaderSearch方法,將Spy類所在的jar包交由啟動類加載器掃描加載,如下代碼所示。


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

在Spy類中打印類加載器,如果打印的結果為null,則說明Spy類是由啟動類加載器加載的。


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


最后,給Spy注入上報方法,在Spy中通過反射調用上報方法,完整的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) {
           }
       }
   }
}


通過反射調用對性能會有所影響,特別是調用鏈路上每個方法都需要反射調用兩個上報方法。


可能不完全理解正確,但筆者試過這個方案確實可行。

實現Agent與應用環境隔離

為什么要實現隔離?


隔離是避免Agent污染應用自身,使開發Java Agent無需考慮引入的jar包是否與目標應用引入的jar包沖突。


Java Agent與Spring Boot應用相遇時會發生什么?


Spring Boot應用打包后,將Agent附著到應用啟動可能會拋出醒目的NoClassDefFoundError異常,這在IDEA中測試是不會發生的,而背后的原因是Agent與打包后的Spring Boot應用使用了不同的類加載器。


我們可能會在Agent中調用被監控的SpringBoot應用的代碼,也可能調用Agent依賴的第三方jar包的API,而這些jar包恰好在SpringBoot應用中也有導入,就可能會出現NoClassDefFoundError。


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


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


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


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


如何實現隔離?


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


參考Alibaba開源的Arthas的實現,自定義URLClassLoader加載agent包以及agent依賴的第三方jar包。


由于premain或者agentmain方法所在的類由jvm使用AppClassLoader所加載,所以必須將agent拆分為兩個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;
       }
       // 優先從parent(SystemClassLoader)里加載系統類,避免拋出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);
   }

}
    

同時在構造方法中指定OnionClassLoader的父類加載器為AppClassLoader的父類加載器。


ClassLoader.getSystemClassLoader():獲取系統類加載器(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、根據agent-core.jar所在絕對路徑構造OnionClassLoader;

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

  • 3、使用反射創建ClassFileTransformer實例;

  • 4、將ClassFileTransformer添加到Instrumentation;


OnionClassFileTransformer類所依賴的agent-core包下的類,自然也會被使用OnionClassLoader類加載器加載,包括agent-core依賴的第三方jar包。


實現Java探針中遇到的問題有哪些

適配webmvc框架

生成分布式調用鏈日記的難點在于方法埋點和方法調用日記串連。


分布式調用鏈日記串連的方式有多種,筆者采用的是最簡單的方式:打點id+打點時間。


對于同進程內的同線程,可用打點id將調用的方法串連起來,根據打點時間與一個累加器的值排序方法調用日記。


對于不同進程,通過傳遞打點id可將不同應用的打點日記串連起來,根據打點時間排序。


例如,適配webmvc框架的目的是從請求頭獲取調用來源傳遞過來的打點ID(事務ID)。對DispatcherServlet#doDispatch方法插樁,從HttpServletRequest參數獲取請求頭“S-Tid”。“S-Tid”是自定義的請求頭參數,用于傳遞打點ID。


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


  • java.lang.TypeNotPresentException:當應用程序試圖使用表示類型名稱的字符串對類型進行訪問,但無法找到帶有指定名稱的類型定義時,拋出該異常。


其原因是,使用asm框架改寫DispatcherServlet類時,asm會使用Class.forName方法加載符號引用的類,如果加載不到目標類則拋出TypeNotPresentException。


默認asm會使用加載自身的類加載器去嘗試加載當前改寫類所依賴的一些類,而加載asm框架使用的類加載器與加載agent-core包使用的是同一個類加載器,DispatcherServlet則由SpringBoot的LaunchedClassLoader類加載器所加載。


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

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

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

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

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

感謝各位的閱讀,以上就是“實現Java探針中遇到的問題有哪些”的內容了,經過本文的學習后,相信大家對實現Java探針中遇到的問題有哪些這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

西城区| 台江县| 揭西县| 刚察县| 昆山市| 龙胜| 双城市| 汉源县| 布拖县| 和平县| 虞城县| 扎兰屯市| 奉化市| 中阳县| 商南县| 宝丰县| 大荔县| 金华市| 那坡县| 皋兰县| 夏河县| 巴彦淖尔市| 凤阳县| 西充县| 华亭县| 象山县| 镇康县| 永寿县| 卓尼县| 页游| 光泽县| 宁津县| 卫辉市| 灌云县| 新干县| 泰顺县| 突泉县| 酒泉市| 东莞市| 九江县| 藁城市|