<ul id="qxxfc"><fieldset id="qxxfc"><tr id="qxxfc"></tr></fieldset></ul>


      我們平時(shí)寫 Java Agent 的機(jī)會確實(shí)不多,也可以說幾乎用不著。但其實(shí)我們一直在用它,而且接觸的機(jī)會非常多。下面這些技術(shù)都使用了 Java Agent
      技術(shù),看一下你就知道為什么了。

      -各個(gè) Java IDE 的調(diào)試功能,例如 eclipse、IntelliJ ;

      -熱部署功能,例如 JRebel、XRebel、 spring-loaded;

      -各種線上診斷工具,例如 Btrace、Greys,還有阿里的 Arthas;

      -各種性能分析工具,例如 Visual VM、JConsole 等;

      Java Agent 直譯過來叫做 Java 代理,還有另一種稱呼叫做 Java 探針。首先說 Java Agent 是一個(gè) jar 包,只不過這個(gè) jar
      包不能獨(dú)立運(yùn)行,它需要依附到我們的目標(biāo) JVM 進(jìn)程中。我們來理解一下這兩種叫法。

      代理:比方說我們需要了解目標(biāo) JVM 的一些運(yùn)行指標(biāo),我們可以通過 Java Agent 來實(shí)現(xiàn),這樣看來它就是一個(gè)代理的效果,我們最后拿到的指標(biāo)是目標(biāo)
      JVM ,但是我們是通過 Java Agent 來獲取的,對于目標(biāo) JVM 來說,它就像是一個(gè)代理;

      探針:這個(gè)說法我感覺非常形象,JVM 一旦跑起來,對于外界來說,它就是一個(gè)黑盒。而 Java Agent 可以像一支針一樣插到 JVM
      內(nèi)部,探到我們想要的東西,并且可以注入東西進(jìn)去。

      拿上面的幾個(gè)我們平時(shí)會用到的技術(shù)舉例子。拿 IDEA 調(diào)試器來說吧,當(dāng)開啟調(diào)試功能后,在 debugger
      面板中可以看到當(dāng)前上下文變量的結(jié)構(gòu)和內(nèi)容,還可以在 watches 面板中運(yùn)行一些簡單的代碼,比如取值賦值等操作。還有 Btrace、Arthas
      這些線上排查問題的工具,比方說有接口沒有按預(yù)期的返回結(jié)果,但日志又沒有錯(cuò)誤,這時(shí),我們只要清楚方法的所在包名、類名、方法名等,不用修改部署服務(wù),就能查到調(diào)用的參數(shù)、返回值、異常等信息。


      上面只是說到了探測的功能,而熱部署功能那就不僅僅是探測這么簡單了。熱部署的意思就是說再不重啟服務(wù)的情況下,保證最新的代碼邏輯在服務(wù)生效。當(dāng)我們修改某個(gè)類后,通過
      Java Agent 的 instrument 機(jī)制,把之前的字節(jié)碼替換為新代碼所對應(yīng)的字節(jié)碼。

      Java Agent 結(jié)構(gòu)


      Java Agent 最終以 jar 包的形式存在。主要包含兩個(gè)部分,一部分是實(shí)現(xiàn)代碼,一部分是配置文件。

      配置文件放在 META-INF 目錄下,文件名為 MANIFEST.MF 。包括以下配置項(xiàng):

      Manifest-Version: 版本號
      Created-By: 創(chuàng)作者
      Agent-Class: agentmain 方法所在類
      Can-Redefine-Classes: 是否可以實(shí)現(xiàn)類的重定義
      Can-Retransform-Classes: 是否可以實(shí)現(xiàn)字節(jié)碼替換
      Premain-Class: premain 方法所在類

      入口類實(shí)現(xiàn) agentmain 和 premain 兩個(gè)方法即可,方法要實(shí)現(xiàn)什么功能就由你的需求決定了。

      Java Agent 實(shí)現(xiàn)和使用

      接下來就來實(shí)現(xiàn)一個(gè)簡單的 Java Agent,基于 Java 1.8,主要實(shí)現(xiàn)兩點(diǎn)簡單的功能:

      1、打印當(dāng)前加載的所有類的名稱;

      2、監(jiān)控一個(gè)特定的方法,在方法中動態(tài)插入簡單的代碼并獲取方法返回值;

      在方法中插入代碼主要是用到了字節(jié)碼修改技術(shù),字節(jié)碼修改技術(shù)主要有 javassist、ASM,已經(jīng) ASM 的高級封裝可擴(kuò)展 cglib,這個(gè)例子中用的是
      javassist。所以需要引入相關(guān)的 maven 包。
      <dependency> <groupId>javassist</groupId> <artifactId>javassist</artifactId>
      <version>3.12.1.GA</version> </dependency>
      實(shí)現(xiàn)入口類和功能邏輯

      入口類上面也說了,要實(shí)現(xiàn) agentmain 和 premain 兩個(gè)方法。這兩個(gè)方法的運(yùn)行時(shí)機(jī)不一樣。這要從 Java Agent
      的使用方式來說了,Java Agent 有兩種啟動方式,一種是以 JVM 啟動參數(shù)-javaagent:xxx.jar 的形式隨著 JVM
      一起啟動,這種情況下,會調(diào)用premain方法,并且是在主進(jìn)程的 main方法之前執(zhí)行。另外一種是以 loadAgent 方法動態(tài) attach 到目標(biāo)
      JVM 上,這種情況下,會執(zhí)行agentmain方法。

      加載方式 執(zhí)行方法
      -javaagent:xxx.jar 參數(shù)形式 premain
      動態(tài) attach agentmain
      代碼實(shí)現(xiàn)如下:
      package kite.lab.custom.agent; import java.lang.instrument.Instrumentation;
      public class MyCustomAgent { /** * jvm 參數(shù)形式啟動,運(yùn)行此方法 * @param agentArgs * @param
      inst */ public static void premain(String agentArgs, Instrumentation inst){
      System.out.println("premain"); customLogic(inst); } /** * 動態(tài) attach 方式啟動,運(yùn)行此方法
      * @param agentArgs * @param inst */ public static void agentmain(String
      agentArgs, Instrumentation inst){ System.out.println("agentmain");
      customLogic(inst); } /** * 打印所有已加載的類名稱 * 修改字節(jié)碼 * @param inst */ private static
      void customLogic(Instrumentation inst){ inst.addTransformer(new
      MyTransformer(), true); Class[] classes = inst.getAllLoadedClasses(); for(Class
      cls :classes){ System.out.println(cls.getName()); } } }
      我們看到這兩個(gè)方法都有參數(shù) agentArgs 和 inst,其中 agentArgs 是我們啟動 Java Agent 時(shí)帶進(jìn)來的參數(shù),比如
      -javaagent:xxx.jar agentArgs。Instrumentation Java
      開放出來的專門用于字節(jié)碼修改和程序監(jiān)控的實(shí)現(xiàn)。我們要實(shí)現(xiàn)的打印已加載類和修改字節(jié)碼也就是基于它來實(shí)現(xiàn)的。其中inst.getAllLoadedClasses()
      一個(gè)方法就實(shí)現(xiàn)了獲取所以已加載類的功能。

      inst.addTransformer方法則是實(shí)現(xiàn)字節(jié)碼修改的關(guān)鍵,后面的參數(shù)就是實(shí)現(xiàn)字節(jié)碼修改的實(shí)現(xiàn)類,代碼如下:
      public class MyTransformer implements ClassFileTransformer { @Override public
      byte[] transform(ClassLoader loader, String className, Class<?>
      classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer)
      throws IllegalClassFormatException { System.out.println("正在加載類:"+ className);
      if (!"kite/attachapi/Person".equals(className)){ return classfileBuffer; }
      CtClass cl = null; try { ClassPool classPool = ClassPool.getDefault(); cl =
      classPool.makeClass(new ByteArrayInputStream(classfileBuffer)); CtMethod
      ctMethod = cl.getDeclaredMethod("test"); System.out.println("獲取方法名稱:"+
      ctMethod.getName()); ctMethod.insertBefore("System.out.println(\" 動態(tài)插入的打印語句
      \");"); ctMethod.insertAfter("System.out.println($_);"); byte[] transformed =
      cl.toBytecode(); return transformed; }catch (Exception e){ e.printStackTrace();
      } return classfileBuffer; } }
      以上代碼的邏輯就是當(dāng)碰到加載的類是 kite.attachapi.Person的時(shí)候,在其中的 test
      方法開始時(shí)插入一條打印語句,打印內(nèi)容是"動態(tài)插入的打印語句",在test方法結(jié)尾處,打印返回值,其中$_就是返回值,這是 javassist 里特定的標(biāo)示符。

      MANIFEST.MF 配置文件

      在目錄 resources/META-INF/ 下創(chuàng)建文件名為 MANIFEST.MF 的文件,在其中加入如下的配置內(nèi)容:
      Manifest-Version: 1.0 Created-By: fengzheng Agent-Class:
      kite.lab.custom.agent.MyCustomAgent Can-Redefine-Classes: true
      Can-Retransform-Classes: true Premain-Class: kite.lab.custom.agent.MyCustomAgent
      配置打包所需的 pom 設(shè)置

      最后 Java Agent 是以 jar 包的形式存在,所以最后一步就是將上面的內(nèi)容打到一個(gè) jar 包里。

      在 pom 文件中加入以下配置
      <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-assembly-plugin</artifactId> <configuration> <archive>
      <manifestFile>src/main/resources/META-INF/MANIFEST.MF</manifestFile> </archive>
      <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef>
      </descriptorRefs> </configuration> </plugin> </plugins> </build>
      用的是 maven 的 maven-assembly-plugin 插件,注意其中要用 manifestFile 指定 MANIFEST.MF
      所在路徑,然后指定 jar-with-dependencies ,將依賴包打進(jìn)去。

      上面這是一種打包方式,需要單獨(dú)的 MANIFEST.MF 配合,還有一種方式,不需要在項(xiàng)目中單獨(dú)的添加 MANIFEST.MF 配置文件,完全在 pom
      文件中配置上即可。
      <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-assembly-plugin</artifactId> <executions> <execution> <goals>
      <goal>attached</goal> </goals> <phase>package</phase> <configuration>
      <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef>
      </descriptorRefs> <archive> <manifestEntries>
      <Premain-Class>kite.agent.vmargsmethod.MyAgent</Premain-Class>
      <Agent-Class>kite.agent.vmargsmethod.MyAgent</Agent-Class>
      <Can-Redefine-Classes>true</Can-Redefine-Classes>
      <Can-Retransform-Classes>true</Can-Retransform-Classes> </manifestEntries>
      </archive> </configuration> </execution> </executions> </plugin> </plugins>
      </build>
      這種方式是將 MANIFEST.MF 的內(nèi)容全部寫作 pom 配置中,打包的時(shí)候就會自動將配置信息生成 MANIFEST.MF 配置文件打進(jìn)包里。

      運(yùn)行打包命令

      接下來就簡單了,執(zhí)行一條 maven 命令即可。
      mvn assembly:assembly
      最后打出來的 jar 包默認(rèn)是以「項(xiàng)目名稱-版本號-jar-with-dependencies.jar」這樣的格式生成到 target 目錄下。

      運(yùn)行打包好的 Java Agent

      首先寫一個(gè)簡單的測試項(xiàng)目,用來作為目標(biāo) JVM,稍后會以兩種方式將 Java Agent 掛到這個(gè)測試項(xiàng)目上。
      package kite.attachapi; import java.util.Scanner; public class RunJvm { public
      static void main(String[] args){ System.out.println("按數(shù)字鍵 1 調(diào)用測試方法"); while
      (true) { Scanner reader = new Scanner(System.in); int number =
      reader.nextInt(); if(number==1){ Person person = new Person(); person.test(); }
      } } }
      以上只有一個(gè)簡單的 main 方法,用 while 的方式保證線程不退出,并且在輸入數(shù)字 1 的時(shí)候,調(diào)用 person.test()方法。

      以下是 Person 類
      package kite.attachapi; public class Person { public String test(){
      System.out.println("執(zhí)行測試方法"); return "I'm ok"; } }
      以命令行的方式運(yùn)行

      因?yàn)轫?xiàng)目是在 IDEA 里創(chuàng)建的,為了省事兒,我就直接在 IDEA 的 「Run/Debug Configurations」里加參數(shù)了。

      -javaagent:/java-agent路徑/lab-custom-agent-1.0-SNAPSHOT-jar-with-dependencies.jar


      然后直接運(yùn)行就可以看到效果了,會看到加載的類名稱。然后輸入數(shù)字鍵 "1",會看到字節(jié)碼修改后的內(nèi)容。



      以動態(tài) attach 的方式運(yùn)行

      測試之前先要把這個(gè)測試項(xiàng)目跑起來,并把之前的參數(shù)去掉。運(yùn)行后,找到這個(gè)它的進(jìn)程id,一般利用jps -l即可。

      動態(tài) attach 的方式是需要代碼實(shí)現(xiàn)的,實(shí)現(xiàn)代碼如下:
      public class AttachAgent { public static void main(String[] args) throws
      Exception{ VirtualMachine vm = VirtualMachine.attach("pid(進(jìn)程號)");
      vm.loadAgent("java-agent路徑/lab-custom-agent-1.0-SNAPSHOT-jar-with-dependencies.jar");
      } }
      運(yùn)行上面的 main 方法 并在測試程序中輸入“1”,會得到上圖同樣的結(jié)果。

      發(fā)現(xiàn)了沒,我們到這里實(shí)現(xiàn)的簡單的功能是不是和 BTrace 和 Arthas
      有點(diǎn)像呢。我們攔截了指定的一個(gè)方法,并在這個(gè)方法里插入了代碼而且拿到了返回結(jié)果。如果把方法名稱變成可配置項(xiàng),并且把返回結(jié)果保存到一個(gè)公共位置,例如一個(gè)內(nèi)存數(shù)據(jù)庫,是不是我們就可以像
      Arthas 那樣輕松的檢測線上問題了呢。當(dāng)然了,Arthas 要復(fù)雜的多,但原理是一樣的。

      sun.management.Agent 的實(shí)現(xiàn)

      不知道你平時(shí)有沒有用過 visualVM 或者 JConsole 之類的工具,其實(shí),它們就是用了 management-agent.jar 這個(gè)Java
      Agent 來實(shí)現(xiàn)的。如果我們希望 Java 服務(wù)允許遠(yuǎn)程查看 JVM 信息,往往會配置上一下這些參數(shù):
      -Dcom.sun.management.jmxremote -Djava.rmi.server.hostname=192.168.1.1
      -Dcom.sun.management.jmxremote.port=9999
      -Dcom.sun.management.jmxremote.rmi.port=9999
      -Dcom.sun.management.jmxremote.authenticate=false
      -Dcom.sun.management.jmxremote.ssl=false
      這些參數(shù)都是 management-agent.jar 定義的。

      我們進(jìn)到 management-agent.jar 包下,看到只有一個(gè) MANIFEST.MF 配置文件,配置內(nèi)容為:
      Manifest-Version: 1.0 Created-By: 1.7.0_07 (Oracle Corporation) Agent-Class:
      sun.management.Agent Premain-Class: sun.management.Agent
      可以看到入口 class 為 sun.management.Agent,進(jìn)到這個(gè)類里面可以找到 agentmain 和
      premain,并可以看到它們的邏輯。在這個(gè)類的開始,能看到我們前面對服務(wù)開啟遠(yuǎn)程 JVM 監(jiān)控需要開啟的那些參數(shù)定義。

      不要吝惜你的「推薦」呦

      歡迎關(guān)注,不定期更新本系列和其他文章
      古時(shí)的風(fēng)箏 ,進(jìn)入公眾號可以加入交流群

      友情鏈接
      ioDraw流程圖
      API參考文檔
      OK工具箱
      云服務(wù)器優(yōu)惠
      阿里云優(yōu)惠券
      騰訊云優(yōu)惠券
      京東云優(yōu)惠券
      站點(diǎn)信息
      問題反饋
      郵箱:[email protected]
      QQ群:637538335
      關(guān)注微信

        <ul id="qxxfc"><fieldset id="qxxfc"><tr id="qxxfc"></tr></fieldset></ul>
          免费看A片视频 | 超碰在线网站 | 黄污视频在线观看 | 在野外被四个男人躁爽口述 | 99香蕉国产精品偷在线观看 | 国产51精品秘 在线观看 | 国产CD系列TS人妖视频 | 午夜福利一二三区 | 日本午夜视频在线播放 | 看大鸡巴操骚逼的视频 |