javaAgent和Java字節碼增強技術的學習與實踐


參考文章:

       https://www.cnblogs.com/chiangchou/p/javassist.html

       https://blog.csdn.net/u010039929/article/details/62881743

       https://www.jianshu.com/p/0f64779cdcea

【本文代碼下載】

  下載代碼

【背景】

  最近在工作中進行程序的性能調優時,想起之前同事的介紹的阿里的Java在線診斷工具 —— arthas,決定試用一下。

  這玩意,是真的好用,能在對被檢測程序 不做 任何改動 和 設置 的情況下,無侵入的對運行中的程序進行性能分析診斷,監控進入指定方法的請求並展示請求的參數,甚至在線熱更新代碼,

  通過查閱資料發現,arthas是基於javaAgent技術 和 Java字節碼增強技術 實現的,所以接下來就開始介紹javaAgent 和 Java字節碼 技術的學習及案例

【知識准備】

 什么是java agent?

  Java agent是在JDK1.5引入的,是一種可以動態修改Java字節碼的技術。java類編譯之后形成字節碼被JVM執行,JVM在執行這些字節碼之前獲取這些字節碼信息,並且對這些字節碼進行修改,來完成一些額外的功能,這種就是java agent技術。

  我們可以使用agent技術構建一個獨立於應用程序的代理程序(即為Agent),用來協助監測、運行甚至替換其他JVM上的程序。使用它可以實現虛擬機級別的AOP功能,並且比起 Spring的 AOP 更加的優美。

  Agent分為兩種,一種是在主程序之前運行的Agent,一種是在主程序之后運行的Agent(前者的升級版,1.6以后提供),待會都會講解

 

 什么是字節碼增強技術?

  個人理解,是在Java字節碼生成之后,運行期對其進行修改,增強其功能,這種方式相當於對應用程序的二進制文件進行修改。Java字節碼增強主要是為了減少冗余代碼,提高性能等。

  通常可以用 ASM 或 javassist 框架來修改字節碼

  

 JVM Attach機制

  jvm attach機制上JVM提供的一種JVM進程間通信的功能,能讓一個進程傳命令給另一個進程,並進行一些內部的操作,比如進行線程dump,那么就需要執行jstack進行,然后把pid等參數傳遞給需要dump的線程來執行,這就是一種java attach。

 Class Transform的實現

  第一次類加載的時候要求被transform的場景,在加載類文件的時候發出ClassFileLoad事件,交給instrument agent來調用java agent里注冊的ClassFileTransformer實現字節碼的修改

【案例】

  在了解java agent 和 字節碼增強技術 后,我們可以結合起來做一個小案例:

在指定類的指定方法執行前,先打印一串11111111

  正如上文所述,Agent分為兩種,一種是在主程序之前運行的Agent,一種是在主程序之后運行的Agent,接下來兩種案例都會給出,本次案例使用javassist 框架

=======================准備好被增強類的代碼=======================

被增強類的pom.xml

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <project xmlns="http://maven.apache.org/POM/4.0.0"
 3          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 5     <parent>
 6         <artifactId>java_agent</artifactId>
 7         <groupId>xcy</groupId>
 8         <version>1.0-SNAPSHOT</version>
 9     </parent>
10     <modelVersion>4.0.0</modelVersion>
11 
12     <artifactId>app</artifactId>
13 
14     <dependencies>
15         <!-- 在被增強類的pom文中,需要加入jdk的tools工具,或者自己把該jar包放入到項目中引用,因為我發現在默認情況下,idea即便引用jdk,用的包居然都是jdk中的jre,jre中沒有tools.jar這個包,就會導致被增強類啟動后,沒有attach服務,無法連接過來 -->
16         <dependency>
17             <groupId>jdk.tools</groupId>
18             <artifactId>jdk.tools</artifactId>
19             <version>jdk1.8.0_121</version>
20             <scope>system</scope>
21             <systemPath>F:/ProgramFiles/java/jdk1.8.0_121/lib/tools.jar</systemPath>
22         </dependency>
23     </dependencies>
24 
25     <build>
26 <!--        <finalName>App</finalName>-->
27         <plugins>
28             <plugin>
29                 <groupId>org.apache.maven.plugins</groupId>
30                 <artifactId>maven-compiler-plugin</artifactId>
31                 <configuration>
32                     <source>1.8</source>
33                     <target>1.8</target>
34                     <encoding>utf-8</encoding>
35                 </configuration>
36             </plugin>
37             <plugin>
38                 <groupId>org.apache.maven.plugins</groupId>
39                 <artifactId>maven-shade-plugin</artifactId>
40                 <version>1.2.1</version>
41                 <executions>
42                     <execution>
43 <!--                        <phase>package</phase>-->
44                         <goals>
45                             <goal>shade</goal>
46                         </goals>
47                         <configuration>
48                             <transformers>
49                                 <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
50                                     <mainClass>com.app.App</mainClass>
51                                 </transformer>
52                             </transformers>
53                         </configuration>
54                     </execution>
55                 </executions>
56             </plugin>
57         </plugins>
58     </build>
59 </project>

 

被增強的類

  備注:代碼在java_agent項目中的app模塊下

   

 1 package com.app;
 2 
 3 public class App { 4 public static void main(String[] args) { 5  hello(); 6  } 7 8 public static void hello() { 9 System.out.println("hello"); 10  } 11 }

 

=======================主程序之前運行的Agent方式=======================

  備注:代碼在java_agent項目中的agent_non_attach模塊下

pom.xml

  agent通過MANIFEST.MF 中指定的Premain-Class參數,獲取代理程序入口,所以寫好的代理類想要運行,在打 jar 包前,還需要要在 MANIFEST.MF 中指定代理程序入口。

  在pom文件中,配置好Premain-Class,就會封裝到META-INF下的MANIFEST.MF中

  

  備注:與 agent 相關的參數

  • Premain-Class :JVM 啟動時指定了代理,此屬性指定代理類,即包含 premain 方法的類
  • Agent-Class :JVM動態加載代理,此屬性指定代理類,即包含 agentmain 方法的類
  • Boot-Class-Path :設置引導類加載器搜索的路徑列表,列表中的路徑由一個或多個空格分開。
  • Can-Redefine-Classes :布爾值(true 或 false)。是否能重定義此代理所需的類
  • Can-Retransform-Classes :布爾值(true 或 false)。是否能重轉換此代理所需的類
  • Can-Set-Native-Method-Prefix :布爾值(true 或 false)。是否能設置此代理所需的本機方法前綴
 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <project xmlns="http://maven.apache.org/POM/4.0.0"
 3          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 5     <parent>
 6         <artifactId>java_agent</artifactId>
 7         <groupId>xcy</groupId>
 8         <version>1.0-SNAPSHOT</version>
 9     </parent>
10     <modelVersion>4.0.0</modelVersion>
11 
12     <artifactId>agent_non_attach</artifactId>
13 
14     <dependencies>
15         <!-- https://mvnrepository.com/artifact/org.javassist/javassist -->
16         <dependency>
17             <groupId>org.javassist</groupId>
18             <artifactId>javassist</artifactId>
19             <version>3.26.0-GA</version>
20         </dependency>
21     </dependencies>
22 
23     <build>
24         <plugins>
25             <plugin>
26                 <groupId>org.apache.maven.plugins</groupId>
27                 <artifactId>maven-compiler-plugin</artifactId>
28                 <configuration>
29                     <source>1.8</source>
30                     <target>1.8</target>
31                     <encoding>utf-8</encoding>
32                 </configuration>
33             </plugin>
34             <plugin>
35                 <groupId>org.apache.maven.plugins</groupId>
36                 <artifactId>maven-shade-plugin</artifactId>
37                 <version>3.0.0</version>
38                 <executions>
39                     <execution>
40                         <phase>package</phase>
41                         <goals>
42                             <goal>shade</goal>
43                         </goals>
44                         <configuration>
45                             <transformers>
46                                 <transformer
47                                         implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
48                                     <manifestEntries>
49                                         <Premain-Class>com.agent.non.attach.demo.Agent</Premain-Class>
50                                     </manifestEntries>
51                                 </transformer>
52                             </transformers>
53                         </configuration>
54                     </execution>
55                 </executions>
56             </plugin>
57         </plugins>
58     </build>
59 </project>

 

Agent代理類

  addTransformer:注冊一個Transformer,從此之后的類加載都會被 transformer 攔截。

  VM啟動后動態加載的 agent,Instrumentation 會通過 agentmain 方法傳入代理程序,agentmain 在 main 函數開始運行后才被調用。

  對於VM啟動時加載的 agent,Instrumentation 會通過 premain 方法傳入代理程序,premain 方法會在程序 main 方法執行之前被調用。此時大部分Java類都沒有被加載(“大部分”是因為,agent類本身和它依賴的類還是無法避免的會先加載的),是一個對類加載埋點做手腳(addTransformer)的好機會。但這種方式有很大的局限性,Instrumentation 僅限於 main 函數執行前,此時有很多類還沒有被加載,如果想為其注入 Instrumentation 就無法辦到。

 1 package com.agent.non.attach.demo;
 2 
 3 import java.lang.instrument.Instrumentation; 4 5 public class Agent { 6 /** 7 * agentArgs 是 premain 函數得到的程序參數,通過 -javaagent 傳入。這個參數是個字符串,如果程序參數有多個,需要程序自行解析這個字符串。 8 * inst 是一個 java.lang.instrument.Instrumentation 的實例,由 JVM 自動傳入。 9 */ 10 public static void premain(String agentOps, Instrumentation inst) { 11 System.out.println("=========premain方法執行========"); 12 // 添加Transformer 13 inst.addTransformer(new MyTransformer("com.app.App", "hello")); 14  } 15 16 /** 17 * 帶有 Instrumentation 參數的 premain 優先級高於不帶此參數的 premain。 18 * 如果存在帶 Instrumentation 參數的 premain,不帶此參數的 premain 將被忽略。 19 * @param agentArgs 20 */ 21 public static void premain(String agentArgs) { 22 23  } 24 }

 

MyTransformer類

  攔截指定方法,進行類增強

 1 package com.agent.non.attach.demo;
 2 
 3 import javassist.ClassPool; 4 import javassist.CtClass; 5 import javassist.CtMethod; 6 7 import java.lang.instrument.ClassFileTransformer; 8 import java.lang.instrument.IllegalClassFormatException; 9 import java.security.ProtectionDomain; 10 11 public class MyTransformer implements ClassFileTransformer { 12 private String targetClassName;//被增強類的類名 13 private String targetMethodName;//被增強的方法名 14 15 public MyTransformer(String targetClassName, String targetMethodName) { 16 this.targetClassName = targetClassName; 17 this.targetMethodName = targetMethodName; 18  } 19 20 public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { 21 className = className.replace("/", "."); 22 if (className.equals(targetClassName)) { 23 CtClass ctclass = null; 24 try { 25 ctclass = ClassPool.getDefault().get(className);// 使用全稱,用於取得字節碼類<使用javassist> 26 CtMethod ctmethod = ctclass.getDeclaredMethod(targetMethodName);// 得到這方法實例 27 ctmethod.insertBefore("System.out.println(1111111);"); 28 return ctclass.toBytecode(); 29 } catch (Exception e) { 30  System.out.println(e.getMessage()); 31  e.printStackTrace(); 32  } 33  } 34 return null; 35  } 36 }

 

運行

 運行有兩種方式:

  1、直接運行,格式:java -javaagent:agent的jar路徑 被增強jar的路徑

   例如:java -jar -javaagent:agent_non_attach-1.0-SNAPSHOT.jar app-1.0-SNAPSHOT.jar

 

  2、在idea中運行

    在被增強類項目中新建一個啟動類,在該類中添加main方法,在運行時,指定VM options參數:-javaagent: agent的jar包路徑 被增強的jar包路徑

    例如:-javaagent:F:\workspace\java_agent\agent_non_attach\target\agent_non_attach-1.0-SNAPSHOT.jar

=======================主程序之后運行的Agent方式=======================

   備注:代碼在java_agent項目中的agent_attach模塊下

被增強類的pom.xml

  在被增強類的pom文中,需要加入jdk的tools工具,或者自己把該jar包放入到項目中引用,因為我發現在默認情況下,idea即便引用jdk,用的包居然都是jdk中的jre,jre中沒有tools.jar這個包,就會導致被增強類啟動后,沒有attach服務,無法連接過來

1 <dependency>
2   <groupId>jdk.tools</groupId>
3   <artifactId>jdk.tools</artifactId>
4    <version>jdk1.8.0_121</version>
5    <scope>system</scope>
6    <systemPath>F:/ProgramFiles/java/jdk1.8.0_121/lib/tools.jar</systemPath>
7 </dependency>

 

KeepRunMain類

  該agent需要在被增強類運行的時候執行,所以需要讓被增強類一直運行

 1 package com.app;
 2 
 3 public class KeepRunMain { 4 public static void main(String[] args) throws InterruptedException { 5 String[] params = {}; 6 while (true) { 7  App.hello(); 8 Thread.sleep(1000L); 9  } 10  } 11 }

 

agent的pom.xml

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <project xmlns="http://maven.apache.org/POM/4.0.0"
 3          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 5     <parent>
 6         <artifactId>java_agent</artifactId>
 7         <groupId>xcy</groupId>
 8         <version>1.0-SNAPSHOT</version>
 9     </parent>
10     <modelVersion>4.0.0</modelVersion>
11 
12     <artifactId>agent_attach</artifactId>
13 
14     <dependencies>
15         <!-- https://mvnrepository.com/artifact/org.javassist/javassist -->
16         <dependency>
17             <groupId>org.javassist</groupId>
18             <artifactId>javassist</artifactId>
19             <version>3.26.0-GA</version>
20         </dependency>
21 
22         <dependency>
23             <groupId>jdk.tools</groupId>
24             <artifactId>jdk.tools</artifactId>
25             <version>jdk1.8.0_121</version>
26             <scope>system</scope>
27             <systemPath>F:/ProgramFiles/java/jdk1.8.0_121/lib/tools.jar</systemPath>
28         </dependency>
29     </dependencies>
30 
31     <build>
32         <plugins>
33             <plugin>
34                 <groupId>org.apache.maven.plugins</groupId>
35                 <artifactId>maven-compiler-plugin</artifactId>
36                 <configuration>
37                     <source>1.8</source>
38                     <target>1.8</target>
39                     <encoding>utf-8</encoding>
40                 </configuration>
41             </plugin>
42             <plugin>
43                 <groupId>org.apache.maven.plugins</groupId>
44                 <artifactId>maven-shade-plugin</artifactId>
45                 <version>3.0.0</version>
46                 <executions>
47                     <execution>
48                         <phase>package</phase>
49                         <goals>
50                             <goal>shade</goal>
51                         </goals>
52                         <configuration>
53                             <transformers>
54                                 <transformer
55                                         implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
56                                     <manifestEntries>
57                                         <Agent-Class>com.agent.attach.demo.Agent</Agent-Class>
58                                         <Can-Retransform-Classes>true</Can-Retransform-Classes>
59                                     </manifestEntries>
60                                 </transformer>
61                             </transformers>
62                         </configuration>
63                     </execution>
64                 </executions>
65             </plugin>
66         </plugins>
67     </build>
68 </project>

 

Agent類

 1 package com.agent.attach.demo;
 2 
 3 import java.lang.instrument.Instrumentation; 4 import java.lang.instrument.UnmodifiableClassException; 5 6 public class Agent { 7 public static String className="com.app.App"; 8 public static String methon="hello"; 9 static { 10 System.out.println("ddd"); 11  } 12 public static void agentmain(String args, Instrumentation inst) throws UnmodifiableClassException { 13 Class[] allClass = inst.getAllLoadedClasses(); 14 for (Class c : allClass) { 15  System.out.println(c.getName()); 16 if(c.getName().equals(className)){ 17 System.out.println("agent loaded"); 18 inst.addTransformer(new MyTransformer(className, methon), true); 19  inst.retransformClasses(c); 20  } 21  } 22  } 23 }

 

MyTransformer類

 1 package com.agent.attach.demo;
 2 
 3 import javassist.ClassPool; 4 import javassist.CtClass; 5 import javassist.CtMethod; 6 7 import java.lang.instrument.ClassFileTransformer; 8 import java.lang.instrument.IllegalClassFormatException; 9 import java.security.ProtectionDomain; 10 11 public class MyTransformer implements ClassFileTransformer { 12 private String targetClassName;//被增強類的類名 13 private String targetMethodName;//被增強的方法名 14 15 public MyTransformer(String targetClassName, String targetMethodName) { 16 this.targetClassName = targetClassName; 17 this.targetMethodName = targetMethodName; 18  } 19 20  @Override 21 public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { 22 try { 23 CtClass ctClass = ClassPool.getDefault().get(this.targetClassName); 24 CtMethod ctMethod=ctClass.getDeclaredMethod(this.targetMethodName); 25  System.out.println(ctMethod.getName()); 26 ctMethod.insertBefore("System.out.println(\" 11111111111111111111111\");"); 27  ctClass.writeFile(); 28 return ctClass.toBytecode(); 29 } catch (Exception e) { 30  System.out.println(e.getMessage()); 31  } 32 return null; 33  } 34 }

 

AttachMain類

 1 package com.agent.attach.demo;
 2 
 3 import com.sun.tools.attach.AttachNotSupportedException; 4 import com.sun.tools.attach.VirtualMachine; 5 import com.sun.tools.attach.VirtualMachineDescriptor; 6 7 import java.util.List; 8 9 public class AttachMain { 10 public static void main(String args[]) throws AttachNotSupportedException { 11  VirtualMachine vm; 12 List<VirtualMachineDescriptor> vmList= VirtualMachine.list(); 13 if(vmList==null || vmList.isEmpty()){ 14 System.out.println("當前沒有java程序運行"); 15 return; 16  } 17 18 //展示所有運行中的java程序 19 System.out.println("當前運行中的java程序:"); 20 for(int i=0;i<vmList.size();i++){ 21 System.out.println("["+i+"] "+vmList.get(i).displayName()+" ,id:"+vmList.get(i).id()+" ,provider:"+vmList.get(i).provider()); 22  } 23 System.out.println("請選擇(輸入序號):"); 24 25 //選擇其中一個java進程進行增強 26 try{ 27 int num=System.in.read()-48; 28 if(num!=-1&&num<vmList.size()){ 29 vm= VirtualMachine.attach(vmList.get(num)); 30 vm.loadAgent("F:\\workspace\\java_agent\\agent_attach\\target\\agent_attach-1.0-SNAPSHOT.jar"); 31  System.in.read(); 32  } 33 }catch(Exception e){ 34  e.printStackTrace(); 35  } 36  } 37 }

 

運行方式

  直接執行AttachMain類的main方法,選擇要增強類的進程即可


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM