1. 程式人生 > >Instrumentation與ClassFileTransformer--位元組碼轉換工具

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種:

  1. 虛擬機器啟動時,通過agent class的premain方法獲得
  2. 虛擬機器啟動後,通過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需要滿足以下條件

  1. agent所在 的jar包的manifest檔案中必須包含Agent-Class屬性,值為agent class。
  2. agent類必須有public static agentmain方法。
  3. 系統類載入器必須支援新增一個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呼叫順序:

  1. Retransformation不可用的
  2. Retransformation不可用的native 的transformation
  3. Retransformation可用的
  4. Retransformation可用的native 的transformation

發生retransformations的時候,Retransformation不可用的transformers不會被呼叫。
同一種transformers按照註冊順序執行。
native的transformers通過ClassFileLoadHook提供。

如果一個transformer不想改變任何程式碼,那麼返回null。否則,應該建立一個新的byte[],不能修改classfileBuffer。

一個transformer丟擲異常,後續的transformer依然會執行,拋異常和返回Null效果相同。