文章目錄
- 前言
- 一、Instrumentation類
-
- 1.addTransformer(ClassFileTransformer transformer, boolean canRetransform)
- 2.addTransformer(ClassFileTransformer transformer)
- 3.removeTransformer(ClassFileTransformer transformer)
- 4.isRetransformClassesSupported()
- 5.retransformClasses(Class… classes) throws UnmodifiableClassException
- 6.isRedefineClassesSupported()
- 7.redefineClasses(ClassDefinition… definitions) throws ClassNotFoundException, UnmodifiableClassException
- 8.isModifiableClass(Class theClass)
- 9.getAllLoadedClasses()
- 10.getInitiatedClasses(ClassLoader loader)
- 11.getObjectSize(Object objectToSize)
- 12.appendToBootstrapClassLoaderSearch(JarFile jarfile)
- 13.appendToSystemClassLoaderSearch(JarFile jarfile)
- 14.isNativeMethodPrefixSupported()
- 15.setNativeMethodPrefix(ClassFileTransformer transformer, String prefix)
- 二、ClassFileTransformer類
-
- 1.transform
- 總結
前言
Instrumentation主要是提供Java代碼增強功能。例如為了提供收集資料的功能,可以通過位元組碼增強技術,對類的方法進行增強。由于代碼是添加的,是以他不會修改原來的狀态。通過這個API,我們可以開發各種工具,例如監控的用戶端,profiles,代碼覆寫率分析工具以及事件的日志埋點工具。
使用方式:
一是在java啟動的時候添加啟動參數javaagent将你的增強包加載進來,會在permain裡把Instrumentation傳過來
二是使用VirtualMachine的API來進行。
具體的做法是調用VirtualMachine.attach(java pid),會在agentmain方法裡把Instrumentation執行個體傳過來
提示:以下是本篇文章介紹其中的方法
一、Instrumentation類
1.addTransformer(ClassFileTransformer transformer, boolean canRetransform)
代碼如下(示例):
函數的作用是注冊一段代碼增強邏輯,它能對所有已定義的類生效(除了通過依賴的transformer進行定義的類)。
當一個類被加載的時候,或者是redefineClasses方法被調用的時候,或者是retransformClasses方法被調用的時候(前提是canRetransform的參數是true),這個函數裡注冊的transformer就被調用了。
至于transformer之間調用的順序,則是由添加的先後順序來決定的(假設有多個agent對同一個類的方法進行了增強,他們是按照先後順序來執行的)可以想象一下管道指令,是一樣的。
異常問題,值得注意的是,如果這個方法在執行的時候發生了異常,jvm不會中斷,他會繼續進行下一個transformer的執行。
重複add的問題,同一個transformer可能會被add多次,這個是不被推薦的,add多次最好new新的對象進行。
當傳入的transformer是null的時候,這個方法會抛出NullPointException的異常,當canRetransform被設為true,而JVM虛拟機被設定為不可被reTransfrom的時候(參見isRetransformClassesSupported方法),會抛出UnsupportedOperationException異常。
2.addTransformer(ClassFileTransformer transformer)
代碼如下(示例):
等同于addTranformer(transformer,false);
3.removeTransformer(ClassFileTransformer transformer)
代碼如下(示例):
移除已注冊的transformer。未來被定義的類将不會被應用這個transformer。由于類加載的多線程語義性,可能被移除的transformer還會依然生效,是以業務方在編寫的時候要處理好,做好防禦性程式設計。
4.isRetransformClassesSupported()
代碼如下(示例):
傳回目前的JVM配置是否支援類的reTransform。這個取決于你的javaagent裡manifest的配置項的Can-Retransform-Classes配置
5.retransformClasses(Class… classes) throws UnmodifiableClassException
代碼如下(示例):
retransformClasses(Class… classes) throws UnmodifiableClassException
函數的用途是對傳入的classes執行transform,一般用于agentmain的方式。因為agentmain的方式是在class已經被加載完了之後attch到jvm上的,這個時候隻有通過這種方式來修改原來的類的行為。
是以在add的時候,canTransform設定為false的将會被忽略,隻有設定為true的才會被執行。如果你的jvm的Can-Retransform-Classes被設定為false,會抛出異常。同時如果你的類有問題,可能會抛出ClassFormatError,NoClassDefFoundError,ClassCircularityError,LinkageError異常,如果傳入的是null,會報NullPointerException
6.isRedefineClassesSupported()
代碼如下(示例):
同isRetransformClassesSupported,在manifest裡配置的
7.redefineClasses(ClassDefinition… definitions) throws ClassNotFoundException, UnmodifiableClassException
代碼如下(示例):
redefineClasses(ClassDefinition… definitions) throws ClassNotFoundException, UnmodifiableClassException
和retransformClasses非常的相似。
retransformClasses是fix-and-continues,而redefined則是直接替換掉了。尤其是有多個agent同時工作的時候,更加推薦retransform。
8.isModifiableClass(Class theClass)
代碼如下(示例):
這個類是否能夠被transform,如果可以則傳回true
9.getAllLoadedClasses()
代碼如下(示例):
擷取所有被JVM加載的類,這個是診斷的好幫手
10.getInitiatedClasses(ClassLoader loader)
代碼如下(示例):
擷取所有被指定的classloader加載的類,如果傳入null則傳回由BootstrapClassl傳回的類
11.getObjectSize(Object objectToSize)
代碼如下(示例):
擷取一個對象消耗的空間大小
12.appendToBootstrapClassLoaderSearch(JarFile jarfile)
代碼如下(示例):
将指定的jar包加載給BootstrapClassLoader,可以被執行多次進而加載多個jar包。查找類的時候會先去BootStrapLoader裡去找,如果找不到就到這個jar包裡面去找。
需要小心處理的是,包名不要重複。例如,在ClassLoader L裡加載了一個類C,他有一個私有類是C$1,如果你的jar包裡正好也有個類C$1,他會先去BootstrapClassLoader裡去找,發現找到了,然後去load,接着發現沒有權限,就會直接報出IllegalAccessError錯誤。
13.appendToSystemClassLoaderSearch(JarFile jarfile)
代碼如下(示例):
特性和appendToBootstrapClassLoader類似,不過差別是這個是用于SystemClassLoader的類的查找的(回憶一下雙親委派機制)。
14.isNativeMethodPrefixSupported()
代碼如下(示例):
是否設定了本地方法的攔截,由manifest設定的。Can-Set-Native-Method-Prefix屬性,具體set native method prefix的用途參考nativeMethodPrefix()
15.setNativeMethodPrefix(ClassFileTransformer transformer, String prefix)
代碼如下(示例):
對本地方法進行代碼增強。但是由于本地方法是沒有位元組碼的,是以他的辦法是重新定義一個同名的函數(原來native method)的名稱,然後将原來的本地方法使用prefix進行重命名。
舉例,以前有一個本地方法 native boolean foo(String x),他的做法是先将native的方法重命名,例如改成
native boolean wapped_foo(String x), 然後再定義一個方法叫
boolean foo(String x) {
—你的代碼— ;
prefix_foo(x)
}
為了防止重複,是以你的prefix最好考慮到方法重複的情況。
正常情況下,按照上面順序執行,但是如果有問題的時候他的執行順序分别是
method(foo) -> nativeImplementation(foo)
method(wrapped_foo) -> nativeImplementation(foo)
method(wrapped_foo) -> nativeImplementation(wrapped_foo)
method(wrapped_foo) -> nativeImplementation(foo)
二、ClassFileTransformer類
用來讓使用者來實作代碼增強邏輯的接口。他隻有一個方法,方法的參數是原來的類的位元組碼以及這個類的classloader對象,傳回值則是被增強之後的類的位元組碼。是以你的代碼是通過已有的類資訊,植入代碼之後,形成新的代碼(類的位元組碼)。
1.transform
代碼如下(示例):
transform(ClassLoader,ClassName,classBeingRedefined,protectionDomain,classfileBuffer)
這裡的來源有兩個,分别是Instrumentation的兩個addInstrumentation()方法。
這個函數被調用的時機有如下幾種情況:
- 這個類第一次被加載,當ClassLoader的defineClass()方法(這個方法是classloader将位元組碼解析成類并且放入metaspace的過程)被調用的時候,這個方法會被call
- Instrumentation#redefineClasses被調用了這個方法的時候,這個方法也會被調用
-
Instrumentation#retransformClasses當這個方法被調用的時候,這個方法也會被調用
他們的執行是按照addInstrumentation的順序來執行的。而reTransformClasses和redefineClasses被執行的時候,他們修改的位元組碼是在之前已經被增強的基礎上進行的。
例如原始方法是foo(String x), 然後在類加載的時候進行了一些增強,插入了一段代碼,那麼在下次retransformClasses執行,這個方法被調用的時候,增加的位元組碼是在上一次的基礎上進行的。換句話說,classfileBuffer永遠都是最後增強執行完成之後的版本。
關于異常和傳回值。如果傳回值是null,代表這次調用什麼都沒做,沒有任何增強代碼加入。他的效果和抛出異常是一樣的,如果在執行這個函數的過程中抛出了異常,則代表本次調用什麼都不做,和傳回null值的效果是一樣的。
總結
本文僅僅簡單介紹了Instrumentation類的方法,具體Instrumentation的使用還需要結合Javassit開源的分析、編輯和建立Java位元組碼的類庫,後續會繼續介紹
參考文檔