Javassist埋點做效能監控
埋點實現在方法前後動態插入程式碼,獲取方法的執行時間。
常見的方法有以下3鍾:
1 硬編碼
2 spirng aop 動態代理
3 動態插入位元組碼
其中 1 和 2 系統程式碼侵入性大,方法3不用更改系統程式碼。
javaAgent技術
JavaAgent是從JDK1.5及以後引入的,在1.5之前無法使用,也可以叫做java代理。利用 java代理,即 java.lang.instrument 做動態 Instrumentation 是 Java SE 5 的新特性,它把 Java 的 instrument 功能從原生代碼中解放出來,使之可以用 Java 程式碼的方式解決問題。
使用 Instrumentation,開發者可以構建一個獨立於應用程式的代理程式(Agent),用來監測和協助執行在 JVM 上的程式,甚至能夠替換和修改某些類的定義。有了這樣的功能,開發者就可以實現更為靈活的執行時虛擬機器監控和 Java 類操作了,這樣的特性實際上提供了一種虛擬機器級別支援的 AOP 實現方式,使得開發者無需對 JDK 做任何升級和改動,就可以實現某些 AOP 的功能了。在 Java SE 6 裡面,instrumentation 包被賦予了更強大的功能:啟動後的 instrument、原生代碼(native code)instrument,以及動態改變 classpath 等等。這些改變,意味著 Java 具有了更強的動態控制、解釋能力,它使得 Java 語言變得更加靈活多變。Instrumentation 的最大作用,就是類定義動態改變和操作。
開發者可以在一個普通 Java 程式(帶有 main 函式的 Java 類)執行時,通過 -javaagent引數指定一個特定的 jar 檔案(包含 Instrumentation 代理)來啟動 Instrumentation 的代理程式。開發者可以讓 Instrumentation 代理在 main 函式執行前執行premain函式。
基本步驟:
1 編寫premian函式
2 將監控程式打包jar,META-INF/MAINIFEST.MF 必須包含 Premain-Class
3 使用java -javaagent:jar 檔案的位置 [= 傳入 premain 的引數 ]執行被監控的程式
新建專案JAgent
1 增加pom依賴
<dependency> <groupId>jboss</groupId> <artifactId>javassist</artifactId> <version>3.8.0.GA</version> </dependency>
2 編寫permian函式
import javassist.*; import java.io.IOException; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.IllegalClassFormatException;import java.lang.instrument.Instrumentation; import java.security.ProtectionDomain; public class JAgent { public static void main(String[] args) { System.out.println("main"); } /** * 在這個 premain 函式中,開發者可以進行對類的各種操作。 * @param agentOps * agentArgs 是 premain 函式得到的程式引數,隨同 “– javaagent”一起傳入。與 main 函式不同的是, * 這個引數是一個字串而不是一個字串陣列,如果程式引數有多個,程式將自行解析這個字串。 * @param inst * 是一個 java.lang.instrument.Instrumentation 的例項, * 由 JVM 自動傳入。java.lang.instrument.Instrumentation 是 instrument 包中定義的一個介面, * 也是這個包的核心部分,集中了其中幾乎所有的功能方法,例如類定義的轉換和操作等等。 */ public static void premain(String agentOps, Instrumentation inst) { System.out.println("premain:"+agentOps); inst.addTransformer(new ClassFileTransformer() { @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { //判斷要埋點的類 if(!"com/chy/JSercice".equals(className)) { return null; } try { ClassPool classPool=new ClassPool(); classPool.insertClassPath(new LoaderClassPath(loader)); CtClass ctClass= classPool.get(className.replace("/",".")); CtMethod ctMethod= ctClass.getDeclaredMethod("run"); //插入本地變數 ctMethod.addLocalVariable("begin",CtClass.longType); ctMethod.addLocalVariable("end",CtClass.longType); ctMethod.insertBefore("begin=System.currentTimeMillis();System.out.println(\"begin=\"+begin);"); //前面插入:最後插入的放最上面 ctMethod.insertBefore("System.out.println( \"埋點開始-2\" );"); ctMethod.insertBefore("System.out.println( \"埋點開始-1\" );"); ctMethod.insertAfter("end=System.currentTimeMillis();System.out.println(\"end=\"+end);"); ctMethod.insertAfter("System.out.println(\"效能:\"+(end-begin)+\"毫秒\");"); //後面插入:最後插入的放最下面 ctMethod.insertAfter("System.out.println( \"埋點結束-1\" );"); ctMethod.insertAfter("System.out.println( \"埋點結束-2\" );"); return ctClass.toBytecode(); } catch (NotFoundException e) { e.printStackTrace(); } catch (CannotCompileException e) { e.printStackTrace(); } catch (IOException e){ e.printStackTrace(); } return new byte[0]; } }); } }
3 打包jar
<build> <plugins> <!--編譯Java原始碼--> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.3</version> <configuration> <source>1.7</source> <target>1.7</target> </configuration> </plugin> <!-- 打成jar時,設定manifestEntries的引數 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>3.0.2</version> <configuration> <archive> <manifestEntries> <Project-name>${project.name}</Project-name> <Project-version>${project.version}</Project-version> <Premain-Class>com.chy.JAgent</Premain-Class> <Can-Redefine-Classes>false</Can-Redefine-Classes> </manifestEntries> </archive> <skip>true</skip> </configuration> </plugin> <!-- 方法1: 包含所有依賴的jar檔案,依賴以class的方式存在 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>1.2.1</version> <configuration> </configuration> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <mainClass>com.chy.JAgent</mainClass> </transformer> </transformers> </configuration> </execution> </executions> </plugin></plugins> </build><Premain-Class>com.chy.JAgent</Premain-Class> 指定 premain 函式入口
確保 META-INF/MAINIFEST.MF 必須包含 Premain-Class
新建專案JAgentTest (埋點專案)
public class App { public static void main( String[] args ) { System.out.println( "JAgentTest is run" ); // run中埋點統計執行時間 new JSercice().run(); } }
public class JSercice { public void call() { String name = "JSercice"; for (int j = 1; j <= 10000; j++) { System.out.println(j); } System.out.println(name + " is end"); } public void run() { System.out.println("JSercice is start"); call(); } }
配置專案 vm 引數
-javaagent:G:\java\intellij_idea\IdeaProjects\javaByteCode\JAgent\target\JAgent-1.0-SNAPSHOT.jar=JAgent
執行JAgentTest 列印結果如下
premain:JAgent
JAgentTest is run
埋點開始-1
埋點開始-2
begin=1530337378023
JSercice is start
..........
JSercice is endend=1530337378024
效能:1毫秒
埋點結束-1
埋點結束-2