Instrumentation 是 java 5 提供的新特性。使用 Instrumentation,開發者可以構建一個代理,用來監測運行在 JVM 上的程序。監測一般是通過在執行某個類文件之前,對該類文件的字節碼進行適當修改進行的。下文將通過一個具體的例子,來展示 java.lang.instrument 包的工作原理,并且實現了一個測量函數運行時間的代理。
簡介
不使用instrumentation 來測量函數運行時間的傳統方法是:在函數調用之前記錄當前系統時間,在函數調用完成之后再次記錄當前系統時間(為了簡化描述,本文不考慮虛擬機進程映射到本地操作系統進程時造成的計時誤差,詳見Use the JVM PRofiler Interface for accurate timing)。最后將兩次數據的差值作為本次函數運行時間返回。這種方法的弱點在于:
使用 instrumentation 提供的功能,結合 Apache 開源項目 BCEL,本文將實現一個用于測量函數運行時間的代理。通過代理技術,用于性能測量的語句與業務邏輯完全分離,同時該代理可以用于測量任意類的任意方法的運行時間,大大提高了代碼的重用性。
Greeting 代理
在實現函數運行時間測量代理之前,我們先通過實現一個簡單的 Greeting 代理,介紹一下 Java 5 中 instrumentation 的原理。每個代理的實現類必須實現 ClassFileTransformer 接口。這個接口提供了一個 public byte[] transform( ClassLoader loader, String className, Class cBR, java.security.ProtectionDomain pD, byte[] classfileBuffer) throws IllegalClassFormatException
方法。通過這個方法,代理可以得到虛擬機載入的類的字節碼(通過 classfileBuffer 參數)。代理的各種功能一般是通過操作這一串字節碼得以實現的。同時還需要提供一個公共的靜態方法: static void premain(String agentArgs, Instrumentation inst)
。一般會在這個方法中創建一個代理對象,通過參數 inst 的 addTransformer() 方法,將創建的代理對象再傳遞給虛擬機。這個方法是一個入口方法,有點類似于一般類的 main 方法。圖1展示了代理工作的原理
圖1 代理工作原理
可以看到,多個代理可以同時執行。這多個代理的 premain 方法將按照代理指定的順序被依次調用。
下面的代碼片斷,演示了 Greeting 代理的 transform 方法。在該方法中我們對 agent 的行為進行了簡單的定制——輸出需要該代理監測的類名。
列表1 輸出 Hello, someClass public byte[] transform(ClassLoader loader, String className, Class cBR, java.security.ProtectionDomain pD, byte[] classfileBuffer) throws IllegalClassFormatException { System.out.println("Hello,/t", className); return null; }
transform 函數的最后,返回 null 值,表示不需要進行類字節碼的轉化。定制完代理的行為之后,創建一個 greeting 代理的實例,將該實例傳遞給虛擬機。
列表2 將 Greeting 代理的實例傳遞給虛擬機public static void premain(String options, Instrumentation ins) { if (options != null) {System.out.printf("I've been called with options: /"%s/"/n", options); } else System.out.println(" I've been called with no options."); ins.addTransformer(new Greeting()); }
options 參數是通過命令行傳遞進來的,類似于調用 main 函數時傳遞的參數。被傳遞進來的命令行參數是一個完整的字符串,不同于 main 方法,該字符串的解析完全由代理自己負責。列表 3 展示了如何使用命令行調用代理:
新聞熱點
疑難解答