天天看點

Instrumentation類方法簡單介紹前言一、Instrumentation類二、ClassFileTransformer類總結

文章目錄

  • 前言
  • 一、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()方法。

這個函數被調用的時機有如下幾種情況:

  1. 這個類第一次被加載,當ClassLoader的defineClass()方法(這個方法是classloader将位元組碼解析成類并且放入metaspace的過程)被調用的時候,這個方法會被call
  2. Instrumentation#redefineClasses被調用了這個方法的時候,這個方法也會被調用
  3. Instrumentation#retransformClasses當這個方法被調用的時候,這個方法也會被調用

    他們的執行是按照addInstrumentation的順序來執行的。而reTransformClasses和redefineClasses被執行的時候,他們修改的位元組碼是在之前已經被增強的基礎上進行的。

例如原始方法是foo(String x), 然後在類加載的時候進行了一些增強,插入了一段代碼,那麼在下次retransformClasses執行,這個方法被調用的時候,增加的位元組碼是在上一次的基礎上進行的。換句話說,classfileBuffer永遠都是最後增強執行完成之後的版本。

關于異常和傳回值。如果傳回值是null,代表這次調用什麼都沒做,沒有任何增強代碼加入。他的效果和抛出異常是一樣的,如果在執行這個函數的過程中抛出了異常,則代表本次調用什麼都不做,和傳回null值的效果是一樣的。

總結

本文僅僅簡單介紹了Instrumentation類的方法,具體Instrumentation的使用還需要結合Javassit開源的分析、編輯和建立Java位元組碼的類庫,後續會繼續介紹

參考文檔