Instrumentation JDK中對它介紹以下:這個類爲JVM上運行時的程序提供測量手段。不少工具經過Instrumenation 修改方法字節碼 實現收集數據目的。這些經過Instrumentaion蒐集數據的工具不會改變程序的狀態和行爲。這些良好的工具包括 monitoring agents , ,profilers, coverage analyzers, 和 event loggers。java
有兩種方式來獲取Instrumentation接口實例:
git
啓動JVM時指定agent類。這種方式,Instrumentation的實例經過agent class的premain方法被傳入。github
JVM提供一種當JVM啓動完成後開啓agent機制。這種狀況下,Instrumention實例經過agent代碼中的的agentmain傳入。編程
java agent 在JDK package specification中解釋:一個agent 是被做爲Jar 文件形式來部署的。在Jar文件中manifest中指定哪一個類做爲agent類。具體的實現包括
maven
經過命令行直接指定選項開啓agent,也支持JVM啓動程序後,經過工具attach到該程序上。ide
下面經過例子來講明javaagent + Instrumentation的用法。
工具
經過在程序啓動前 preagent方式:(該例子實現輸出全部JVM加載類名字,並在People類的 sayHello 方法調用先後加入log)測試
1 people類spa
public class People { public void sayHello(){ System.out.println("hello !!!!"); } }
2 實現一個 ClassFileTransformer類:
.net
agent經過該具體實現來實現轉換加載到JVM中class files。這種類的轉換髮生在類文件被載入JVM以前。所以這能夠實現類AOP編程的效果。
public class PeopleClassFileTransformer implements ClassFileTransformer { /** * 經過javassist修改字節碼 * @param loader * @param className * @param classBeingRedefined * @param protectionDomain * @param classfileBuffer * @return * @throws IllegalClassFormatException */ @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { System.out.println("load class:"+className); if("com.yao.intrumentation.People".equals(className)){ try { //經過javassist修改sayHello方法字節碼 CtClass ctClass= ClassPool.getDefault().get(className.replace('/','.')); CtMethod sayHelloMethod=ctClass.getDeclaredMethod("sayHello"); sayHelloMethod.insertBefore("System.out.println(\"before sayHello----\");"); sayHelloMethod.insertAfter("System.out.println(\"after sayHello----\");"); return ctClass.toBytecode(); } catch (NotFoundException e) { e.printStackTrace(); } catch (CannotCompileException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } return classfileBuffer; } }
3 編寫agent,該類必須包含premain方法。並在META-INF 中添加MANIFEST.MF ,在清單中添加
Premain-Class: com.yao.intrumentation.MyAgent
public class MyAgent { /** * 該方法是一個類做爲agent類必備的 * @param agentArgs * @param inst */ public static void premain(String agentArgs,Instrumentation inst){ //加入ClassFileTransfomer inst.addTransformer(new PeopleClassFileTransformer()); } }
4 打包agent類(這裏能夠把上面的 ClassFileTransfer MyAgent單獨拿出來打包 。這裏爲了方面把全部的代碼都放到一塊兒了。。)
代碼編譯後 在target/classes/下打包 加 m 參數是指定MANIFEST
jar -cvfm myagent.jar META-INF/MANIFEST.MF * // 在本身項目目錄下執行 好比maven目錄結構 // 在編譯後的target/class/下執行
5 測試main類:
public class TestMain { public static void main(String[]args){ People people=new People(); people.sayHello(); } }
啓動 這裏爲了方便解決引用的javassist jar包 classpath問題,我直接在Intellij 指定VM參數啓動上面的main 方法,這樣就不用在命令行裏手工設定classpath。
-javaagent:/Users/yao/workspace/private/JavaSPI/target/classes/myagent.jar 指代我打包的agent jar 位置。
結果輸入以下:
load class:java/lang/invoke/MethodHandleImpl load class:java/lang/invoke/MethodHandleImpl$1 load class:java/lang/invoke/MethodHandleImpl$2 load class:java/util/function/Function load class:java/lang/invoke/MethodHandleImpl$3 load class:java/lang/invoke/MethodHandleImpl$4 load class:java/lang/ClassValue load class:java/lang/ClassValue$Entry load class:java/lang/ClassValue$Identity load class:java/lang/ClassValue$Version load class:java/lang/invoke/MemberName$Factory load class:java/lang/invoke/MethodHandleStatics load class:java/lang/invoke/MethodHandleStatics$1 load class:sun/misc/PostVMInitHook load class:sun/launcher/LauncherHelper load class:com/yao/intrumentation/TestMain load class:sun/launcher/LauncherHelper$FXHelper load class:java/lang/Class$MethodArray load class:java/lang/Void load class:com/yao/intrumentation/People before sayHello---- hello !!!! after sayHello---- load class:java/lang/Shutdown load class:java/lang/Shutdown$Lock
下面簡單介紹經過attach到正在運行的JVM程序的 agentmain方式:
1 編寫agent類
public class MainAgent { public static void agentmain(String args, Instrumentation inst){ Class[] classes = inst.getAllLoadedClasses(); for(Class cls :classes){ System.out.println(cls.getName()); } } }
2 寫一個長時間運行main
public class RunningApp { public static void main(String[]args) throws InterruptedException { People people=new People(); Thread.sleep(1000*1000); } }
3 修改MANIFEST.MF
Agent-Class: com.yao.intrumentation.MainAgent
用相似上面的方法打包成jar
4 編寫attach 程序
public class TestMainAgent { public static void main(String[]args) throws InterruptedException, IOException, AgentLoadException, AgentInitializationException, AttachNotSupportedException { VirtualMachine vm = VirtualMachine.attach(args[0]); //正在運行的java 程序 ps id vm.loadAgent("/Users/yao/workspace/private/JavaSPI/target/classes/agentmain.jar"); //剛剛編譯好的agent jar 位置 } }
運行 把 RunningApp啓動後 jps 拿到ps id ,傳給上面的程序,運行便可看到JVM加載的全部類文件
轉載註明:http://my.oschina.net/robinyao/blog/489767
具體代碼:https://github.com/WangErXiao/JavaSPI/tree/master/src/main/java/com/yao/intrumentation