一個代理實現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效果相同。