Instrumentation與ClassFileTransformer--位元組碼轉換工具
一個代理實現ClassFileTransformer介面用於改變執行時的位元組碼(class File),這個改變發生在jvm載入這個類之前。對所有的類載入器有效。
class File這個術語定義於虛擬機器規範3.1,指的是位元組碼的byte陣列,而不是檔案系統中的class檔案。
介面中只有一個方法:
byte[] transform( ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException;
ClassFileTransformer需要新增到Instrumentation例項中才能生效。
獲取Instrumentation例項的方法有2種:
- 虛擬機器啟動時,通過agent class的premain方法獲得
- 虛擬機器啟動後,通過agent class的agentmain方法獲得
一旦agent引數獲取到一個instrumentation,agent將會在任意時候呼叫例項中的方法。
agent應該以jar包的形式存在,也就是說agent所在的類需要單獨打包一個jar包,jar包的manifest檔案指定agent class。檔案中包含Premain-Class屬性,agent class類必須實現public static premain 方法,實際應用的main方法在這個方法之後執行。
premain 方法有2種簽名,虛擬機器優先呼叫
public static void premain(String agentArgs, Instrumentation inst);
如果沒有上一種,則呼叫下一種
public static void premain(String agentArgs);
通過這個 -javaagent:jarpath[=options] 引數,啟動實際應用,就會自帶agent。如果agent啟動失敗,jvm會終止。
在虛擬機器啟動後,啟動agent需要滿足以下條件
- agent所在 的jar包的manifest檔案中必須包含Agent-Class屬性,值為agent class。
- agent類必須有public static agentmain方法。
- 系統類載入器必須支援新增一個agent的jar包到系統類路徑system class path
這個方法也有2種簽名,優先載入第一種,第一種沒有,就載入第二種。
public static void agentmain(String agentArgs, Instrumentation inst);
public static void agentmain(String agentArgs);
如果agent是在jvm啟動後啟動,那麼premain就不會執行了。也就是說一個agent的2種方法只會啟動一種。premain和agentmain是二選一的。
agentmain丟擲異常,不會導致jvm終止。
第二種啟動方式,先用jps獲取程序id,然後啟動agentjar包。
VirtualMachine 在jdk的lib下面的tools.jar中,如果不在classpath的話,要加進去。
VirtualMachine vm = VirtualMachine.attach("3134");
try {
vm.loadAgent("/../agent.jar");
} finally
{
vm.detach();
}
agent的jar包中manifest中可以有的屬性:
屬性 | 作用 |
---|---|
Premain-Class | 指定代理類 |
Agent-Class | 指定代理類 |
Boot-Class-Path | 指定bootstrap類載入器的搜尋路徑,在平臺指定的查詢路徑失敗的時候生效, 可選 |
Can-Redefine-Classes | 是否需要重新定義所有類,預設為false,可選。 |
Can-Retransform-Classes | 是否需要retransform,預設為false,可選 |
有兩種ClassFileTransformer,根據canRetransform決定是哪一種。
在向Instrumentation#addTransformer新增轉換器的時候,會指定canRetransform,預設為false。決定retransformation是否可用。
一旦一個transformer被註冊到instrumentation中,每當一個類被定義(ClassLoader.defineClass)或被重新定義(Instrumentation.redefineClasses)時,它都會被呼叫。
如果retransformation可用,那麼一個類被retransformation(Instrumentation.retransformClasses)時,transformer也會被呼叫。
存在多個transformers時,每個transformer會進行鏈式呼叫。
多個transformers呼叫順序:
- Retransformation不可用的
- Retransformation不可用的native 的transformation
- Retransformation可用的
- Retransformation可用的native 的transformation
發生retransformations的時候,Retransformation不可用的transformers不會被呼叫。
同一種transformers按照註冊順序執行。
native的transformers通過ClassFileLoadHook提供。
如果一個transformer不想改變任何程式碼,那麼返回null。否則,應該建立一個新的byte[],不能修改classfileBuffer。
一個transformer丟擲異常,後續的transformer依然會執行,拋異常和返回Null效果相同。