一、原理:基於javaAgent和Java字節碼注入技術的java探針工具技術原理html
二、原理分析java
動態代理功能實現說明,咱們利用javaAgent和ASM字節碼技術開發java探針工具,實現原理以下:瀏覽器
jdk1.5之後引入了javaAgent技術,javaAgent是運行方法以前的攔截器。咱們利用javaAgent和ASM字節碼技術,在JVM加載class二進制文件的時候,利用ASM動態的修改加載的class文件,在監控的方法先後添加計時器功能,用於計算監控方法耗時,同時將方法耗時及內部調用狀況放入處理器,處理器利用棧先進後出的特色對方法調用前後順序作處理,當一個請求處理結束後,將耗時方法軌跡和入參map輸出到文件中,而後根據map中相應參數或耗時方法軌跡中的關鍵代碼區分出咱們要抓取的耗時業務。最後將相應耗時軌跡文件取下來,轉化爲xml格式並進行解析,經過瀏覽器將代碼分層結構展現出來,方便耗時分析,如圖下圖所示。app
Java探針工具功能點:ide
一、支持方法執行耗時範圍抓取設置,根據耗時範圍抓取系統運行時出如今設置耗時範圍的代碼運行軌跡。工具
二、支持抓取特定的代碼配置,方便對配置的特定方法進行抓取,過濾出關係的代碼執行耗時狀況。post
三、支持APP層入口方法過濾,配置入口運行前的方法進行監控,至關於監控特有的方法耗時,進行方法專題分析。ui
四、支持入口方法參數輸出功能,方便跟蹤耗時高的時候對應的入參數。this
五、提供WEB頁面展現接口耗時展現、代碼調用關係圖展現、方法耗時百分比展現、可疑方法凸顯功能。命令行
三、實例:
JavaAgent 是JDK 1.5 之後引入的,也能夠叫作Java代理。
JavaAgent 是運行在 main方法以前的攔截器,它內定的方法名叫 premain ,也就是說先執行 premain 方法而後再執行 main 方法。
查看原做者實例地址:https://www.cnblogs.com/aspirant/p/8796974.html
JavaAgent 的應用場景
JDK5中只能經過命令行參數在啓動JVM時指定javaagent參數來設置代理類,而JDK6中已經不只限於在啓動JVM時經過配置參數來設置代理類,JDK6中經過 Java Tool API 中的 attach 方式,咱們也能夠很方便地在運行過程當中動態地設置加載代理類,以達到 instrumentation 的目的。
Instrumentation 的最大做用,就是類定義動態改變和操做。
最簡單的一個例子,計算某個方法執行須要的時間,不修改源代碼的方式,使用Instrumentation 代理來實現這個功能,給力的說,這種方式至關於在JVM級別作了AOP支持,這樣咱們能夠在不修改應用程序的基礎上就作到了AOP。
package com.shanhy.demo.agent; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.IllegalClassFormatException; import java.security.ProtectionDomain; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javassist.ClassPool; import javassist.CtClass; import javassist.CtMethod; import javassist.CtNewMethod; /** * 檢測方法的執行時間 * */ public class MyTransformer implements ClassFileTransformer { final static String prefix = "\nlong startTime = System.currentTimeMillis();\n"; final static String postfix = "\nlong endTime = System.currentTimeMillis();\n"; // 被處理的方法列表 final static Map<String, List<String>> methodMap = new HashMap<String, List<String>>(); public MyTransformer() { add("com.shanhy.demo.TimeTest.sayHello"); add("com.shanhy.demo.TimeTest.sayHello2"); } private void add(String methodString) { String className = methodString.substring(0, methodString.lastIndexOf(".")); String methodName = methodString.substring(methodString.lastIndexOf(".") + 1); List<String> list = methodMap.get(className); if (list == null) { list = new ArrayList<String>(); methodMap.put(className, list); } list.add(methodName); } @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { className = className.replace("/", "."); if (methodMap.containsKey(className)) {// 判斷加載的class的包路徑是否是須要監控的類 CtClass ctclass = null; try { ctclass = ClassPool.getDefault().get(className);// 使用全稱,用於取得字節碼類<使用javassist> for (String methodName : methodMap.get(className)) { String outputStr = "\nSystem.out.println(\"this method " + methodName + " cost:\" +(endTime - startTime) +\"ms.\");"; CtMethod ctmethod = ctclass.getDeclaredMethod(methodName);// 獲得這方法實例 String newMethodName = methodName + "$old";// 新定義一個方法叫作好比sayHello$old ctmethod.setName(newMethodName);// 將原來的方法名字修改 // 建立新的方法,複製原來的方法,名字爲原來的名字 CtMethod newMethod = CtNewMethod.copy(ctmethod, methodName, ctclass, null); // 構建新的方法體 StringBuilder bodyStr = new StringBuilder(); bodyStr.append("{"); bodyStr.append(prefix); bodyStr.append(newMethodName + "($$);\n");// 調用原有代碼,相似於method();($$)表示全部的參數 bodyStr.append(postfix); bodyStr.append(outputStr); bodyStr.append("}"); newMethod.setBody(bodyStr.toString());// 替換新方法 ctclass.addMethod(newMethod);// 增長新方法 } return ctclass.toBytecode(); } catch (Exception e) { System.out.println(e.getMessage()); e.printStackTrace(); } } return null; } }