關(guān)于 java Agent

參考資料

instrument 規(guī)范

https://docs.oracle.com/javase/8/docs/api/java/lang/instrument/package-summary.html?is-external=true

Class VirtualMachine

https://docs.oracle.com/javase/8/docs/jdk/api/attach/spec/com/sun/tools/attach/VirtualMachine.html#loadAgent-java.lang.String-

Interface ClassFileTransformer

https://docs.oracle.com/javase/8/docs/api/java/lang/instrument/ClassFileTransformer.html


java agent 實現(xiàn)

代理需要編譯成jar包的方式來運行篙骡,JAR文件manifest中的屬性指定將加載哪個代理類來啟動代理傅物。

有下面兩種方式可以啟動一個agent

一:命令行接口

程序沒有啟動時可以通過在命令行上指定javaagent的方式來啟動代理

-javaagent:jarpath[=options]

#如:
java -javaagent:xxx-agent.jar -cp xxx.jar com.wwh.xxxx

通過命令行的方式可以指定多個代理,并且支持參數(shù)苫费。初始化Java虛擬機(jī)(JVM)之后珍手,將按照指定代理的順序調(diào)用每個premain方法办铡,然后調(diào)用真正的應(yīng)用程序main方法。每個premain方法必須返回琳要,以便繼續(xù)啟動程序寡具。

agent Jar包中的manifest文件必須包含Premain-Class, 指向代理的入口類稚补,這個類中包含了一個公共靜態(tài)的premain方法

premain 方法有兩種簽名童叠,虛擬機(jī)會嘗試先運行下面這個

public static void premain(String agentArgs, Instrumentation inst);

如果沒有會嘗試調(diào)用下面這個

public static void premain(String agentArgs);

當(dāng)使用命令行選項啟動代理時,不會調(diào)用agentmain方法课幕。

代理類將由系統(tǒng)類加載器加載(參見ClassLoader.getSystemClassLoader)厦坛。這是類加載器,它通常加載包含應(yīng)用程序主方法的類乍惊。premain方法將在與應(yīng)用程序main方法相同的安全性和類加載器規(guī)則下運行杜秸。對于代理premain方法可以做什么,沒有建模限制润绎。application main可以做的任何事情撬碟,包括創(chuàng)建線程诞挨,都是合法的。

每個代理都通過agentArgs參數(shù)傳遞其代理選項呢蛤。代理選項作為單個字符串傳遞惶傻,任何額外的解析都應(yīng)該由代理本身執(zhí)行。

二:在虛擬機(jī)啟動之后再啟動代理

程序已經(jīng)啟動后可以通過VirtualMachine 來加載啟動代理其障,如下:

VirtualMachine vm = VirtualMachine.attach("2177");
vm.loadAgent(jar);
vm.detach();

注意:

  1. 代理JAR的manifest中必須包含屬性 Agent-Class银室。此屬性的值是代理類的名稱。
  2. 代理類必須實現(xiàn)一個公共靜態(tài)的 agentmain 方法励翼,如下所示蜈敢。

啟動代理時先嘗試運行

public static void agentmain(String agentArgs, Instrumentation inst);

找不到再嘗試運行

public static void agentmain(String agentArgs);

agentmain方法不能阻塞,這個類同用可以擁有 premain 方法抚笔,不過并不會被調(diào)用

參數(shù)通過如下方式指定:

vm.loadAgent(jar, options);

Manifest 屬性說明

代理JAR文件定義了以下清單屬性:

  1. Premain-Class 此屬性指定代理類扶认,也就是包含premain方法的類。如果該屬性不存在,JVM將中止踩验。注意這是一個類名拒课,而不是文件名或路徑。
  2. Agent-Class 指定代理類海洼,支持在VM啟動后啟動代理的機(jī)制,包含了agentmain 方法的類,如果該屬性不存在敞葛,則代理將不會啟動。注意這是一個類名与涡,而不是文件名或路徑惹谐。
  3. Boot-Class-Path 引導(dǎo)類裝入器要搜索的路徑列表
  4. Can-Redefine-Classes 布爾值( true 或 false ,不區(qū)分大小寫)驼卖。代理是否可以重新定義類氨肌。此屬性是可選的,默認(rèn)為false酌畜。
  5. Can-Retransform-Classes 布爾值( true 或 false 怎囚,不區(qū)分大小寫)。代理是否可以重新轉(zhuǎn)換類桥胞。此屬性是可選的恳守,默認(rèn)為false。
  6. Can-Set-Native-Method-Prefix 布爾值( true 或 false 贩虾,不區(qū)分大小寫)催烘。代理是否可以設(shè)置本地方法前綴。此屬性是可選的缎罢,默認(rèn)為false伊群。

代理JAR文件可同時具有清單中的Premain-Class和Agent-Class屬性喳钟。當(dāng)使用-javaagent選項在命令行上啟動代理時,執(zhí)行Premain-Class屬性指定的
代理類在岂,而Agent-Class屬性將被忽略奔则。類似地,如果代理在VM啟動之后再啟動蔽午,則執(zhí)行Agent-Class屬性指定的代理類易茬,而忽略Premain-Class屬性的值。


相關(guān)類說明

幾個關(guān)鍵類和接口

VirtualMachine

表示一個java虛擬機(jī)
VirtualMachine表示已附加到的Java虛擬機(jī)及老。它所附加的Java虛擬機(jī)有時稱為目標(biāo)虛擬機(jī)或目標(biāo)VM抽莱。應(yīng)用程序(通常是managemet控制臺或分析器之類的工具)使用虛擬機(jī)將代理加載到目標(biāo)VM中。例如骄恶,用Java語言編寫的分析器工具可能附加到正在運行的應(yīng)用程序食铐,并加載其分析器代理來分析正在運行的應(yīng)用程序。

通過調(diào)用帶有標(biāo)識目標(biāo)虛擬機(jī)的標(biāo)識符的attach方法來獲得虛擬機(jī)僧鲁。標(biāo)識符依賴于實現(xiàn)虐呻,但在每個Java虛擬機(jī)都在自己的操作系統(tǒng)進(jìn)程中運行的環(huán)境中,標(biāo)識符通常是進(jìn)程標(biāo)識符(或pid)寞秃。另外斟叼,通過使用從list方法返回的虛擬機(jī)描述符列表中獲得的VirtualMachineDescriptor調(diào)用attach方法來獲得虛擬機(jī)實例。一旦獲得對虛擬機(jī)的引用春寿,就使用loadAgent朗涩、loadAgentLibrary和loadAgentPath方法將代理加載到目標(biāo)虛擬機(jī)中。loadAgent方法用于加載用Java語言編寫并部署在JAR文件中的代理绑改。loadAgentLibrary和loadAgentPath方法用于加載部署在動態(tài)庫或靜態(tài)鏈接到VM并使用JVM工具接口的代理谢床。

除了加載代理之外,虛擬機(jī)還提供對目標(biāo)VM中的系統(tǒng)屬性的讀訪問厘线。這在某些環(huán)境中非常有用识腿,比如java。home皆的、os.name或os覆履。arch用于構(gòu)造將加載到目標(biāo)VM的代理的路徑。

一個啟動jmx的例子

      // attach to target VM
      VirtualMachine vm = VirtualMachine.attach("2177");

      // start management agent
      Properties props = new Properties();
      props.put("com.sun.management.jmxremote.port", "5000");
      vm.startManagementAgent(props);

      // detach
      vm.detach();

虛擬機(jī)對于多個并發(fā)線程的使用是安全的费薄。

ClassFileTransformer

代理提供此接口的實現(xiàn)硝全,以便轉(zhuǎn)換類文件。轉(zhuǎn)換發(fā)生在JVM定義類之前楞抡。

一個代理提供者需要實現(xiàn):ClassFileTransformer 接口伟众,來轉(zhuǎn)變class文件,這個接口有一個方法

    byte[]
    transform(  ClassLoader         loader,
                String              className,
                Class<?>            classBeingRedefined,
                ProtectionDomain    protectionDomain,
                byte[]              classfileBuffer)
        throws IllegalClassFormatException;

此方法的實現(xiàn)可以轉(zhuǎn)換提供的類文件并返回一個新的替換類文件

一旦向addTransformer注冊了一個transformer召廷,每個新的類定義和每個類重定義都會調(diào)用這個transformer凳厢。每個類重新轉(zhuǎn)換時也將調(diào)用具有重新轉(zhuǎn)換能力的轉(zhuǎn)換器账胧。
對新類定義的請求是用 ClassLoader.defineClass 或本地調(diào)用觸發(fā)的。
對類的重定義請求是通過 Instrumentation.redefineClasses 或本地調(diào)用觸發(fā)的先紫。
對類重新轉(zhuǎn)換的請求是通過 Instrumentation.retransformClasses 或本地調(diào)用觸發(fā)的治泥。
在處理請求期間,在類文件字節(jié)被驗證或應(yīng)用之前調(diào)用轉(zhuǎn)換器遮精。
當(dāng)有多個轉(zhuǎn)換器時居夹,轉(zhuǎn)換由鏈接轉(zhuǎn)換調(diào)用組成。也就是說本冲,一個轉(zhuǎn)換調(diào)用返回的字節(jié)數(shù)組將成為下一個調(diào)用的輸入(通過classfileBuffer參數(shù))准脂。

關(guān)于transform輸入的classfileBuffer參數(shù):
如果實現(xiàn)方法確定不需要轉(zhuǎn)換,則返回null檬洞。否則狸膏,它應(yīng)該創(chuàng)建一個新的byte[]數(shù)組,將輸入classfileBuffer連同所有所需的轉(zhuǎn)換復(fù)制到其中添怔,并返回新數(shù)組湾戳。不能修改輸入classfileBuffer。

在重新轉(zhuǎn)換和重新定義的情況下澎灸,轉(zhuǎn)換器必須支持重新定義語義:如果轉(zhuǎn)換器在初始定義期間更改的類稍后被重新轉(zhuǎn)換或重新定義院塞,轉(zhuǎn)換器必須確保第二個類輸出類文件是第一個輸出類文件的合法重新定義。

如果transformer拋出異常(它沒有捕獲異常)性昭,后續(xù)的transformer仍然會被調(diào)用,并且負(fù)載县遣、重新定義或重新轉(zhuǎn)換仍然會被嘗試糜颠。因此,拋出異常的效果與返回null相同萧求。為了防止在transformer代碼中生成未檢查異常時出現(xiàn)意外行為其兴,transformer可以捕獲Throwable。如果轉(zhuǎn)換器認(rèn)為classFileBuffer不代表一個有效格式化的類文件夸政,它應(yīng)該拋出一個IllegalClassFormatException;而這與返回null具有相同的效果元旬。它有助于記錄或調(diào)試格式錯誤。

參數(shù)說明:

  1. loader 要轉(zhuǎn)換的類的定義類加載器守问,如果是bootstrap loader則為空
  2. className 類名的內(nèi)部形式為Java虛擬機(jī)規(guī)范中定義的完全限定類名和接口名匀归。例如:"java/util/List"。
  3. classBeingRedefined 如果這是由重新定義或重新轉(zhuǎn)換觸發(fā)的耗帕,則這個類存在重新定義或重新轉(zhuǎn)換穆端,否則為null。
  4. protectionDomain 正在定義或重新定義的類的保護(hù)域
  5. classfileBuffer 類文件格式的輸入字節(jié)緩沖區(qū)-不能修改

返回:
格式良好的類文件緩沖區(qū)(轉(zhuǎn)換的結(jié)果)仿便,如果沒有執(zhí)行轉(zhuǎn)換体啰,則為null攒巍。

Instrumentation

該類提供測試Java編程語言代碼所需的服務(wù)。插裝是將字節(jié)碼添加到方法中荒勇,以便收集工具使用的數(shù)據(jù)柒莉。由于這些更改純粹是附加的,所以這些工具不會修改應(yīng)用程序狀態(tài)或行為沽翔。此類良性工具的例子包括監(jiān)視代理常柄、分析器、覆蓋率分析器和事件日志記錄器搀擂。

獲取Instrumentation 接口實例有兩種方法:

  1. 當(dāng)JVM以指示代理類的方式啟動時西潘。在這種情況下,將一個插裝實例傳遞給代理類的premain方法哨颂。
  2. 當(dāng)JVM在啟動后的某個時候提供啟動代理的機(jī)制時喷市。在這種情況下,將一個插裝實例傳遞給代理代碼的agentmain方法威恼。

一旦代理獲得一個Instrumentation 實例品姓,代理可以在任何時候調(diào)用該實例上的方法。

Instrumentation.addTransformer(new Transformer(), true);  

第二個參數(shù)表示是否可以重新轉(zhuǎn)換已經(jīng)定義好了的類
對于啟動后再附加agent的方式箫措,如果想要改變已經(jīng)加載了的類腹备,需要設(shè)置為true

并且注意修改manifest文件中的

Can-Retransform-Classes: true

否則會報錯:

adding retransformable transformers is not supported in this environment

示例

pom 文件示例:
<dependencies>
    <dependency>
        <groupId>jdk.tools</groupId>
        <artifactId>jdk.tools</artifactId>
        <version>1.8</version>
        <scope>system</scope>
        <systemPath>${JAVA_HOME}/lib/tools.jar</systemPath>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-jar-plugin</artifactId>
            <configuration>
                <archive>
                    <manifest>
                        <addClasspath>true</addClasspath>
                    </manifest>
                    <manifestEntries>
                        <!-- 參數(shù)方式啟動agent需要這個 -->
                        <Premain-Class>
                            com.wwh.agentmain.AgentMain
                        </Premain-Class>

                        <!-- 啟動后附加啟動agent需要這個 -->
                        <Agent-Class>
                            com.wwh.agentmain.AgentMain
                        </Agent-Class>

                        <!-- 是否可以重新轉(zhuǎn)換類 -->
                        <Can-Retransform-Classes>
                            true
                        </Can-Retransform-Classes>

                        <!-- 是否可以重新定義類 -->
                        <Can-Redefine-Classes>
                            true
                        </Can-Redefine-Classes>

                    </manifestEntries>
                </archive>
            </configuration>
        </plugin>
    </plugins>
</build>
manifest文件示例:

META-INF/MANIFEST.MF

Manifest-Version: 1.0
Premain-Class: com.wwh.agentmain.AgentMain
Archiver-Version: Plexus Archiver
Built-By: Administrator
Agent-Class: com.wwh.agentmain.AgentMain
Can-Redefine-Classes: true
Can-Retransform-Classes: true
Created-By: Apache Maven 3.5.3
Build-Jdk: 1.8.0_151

代碼例子

https://github.com/wangwen135/java-agent-test

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市斤蔓,隨后出現(xiàn)的幾起案子植酥,更是在濱河造成了極大的恐慌,老刑警劉巖弦牡,帶你破解...
    沈念sama閱讀 222,729評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件友驮,死亡現(xiàn)場離奇詭異,居然都是意外死亡驾锰,警方通過查閱死者的電腦和手機(jī)卸留,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,226評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來椭豫,“玉大人耻瑟,你說我怎么就攤上這事∩退郑” “怎么了喳整?”我有些...
    開封第一講書人閱讀 169,461評論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長今缚。 經(jīng)常有香客問我算柳,道長,這世上最難降的妖魔是什么姓言? 我笑而不...
    開封第一講書人閱讀 60,135評論 1 300
  • 正文 為了忘掉前任瞬项,我火速辦了婚禮蔗蹋,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘囱淋。我一直安慰自己猪杭,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 69,130評論 6 398
  • 文/花漫 我一把揭開白布妥衣。 她就那樣靜靜地躺著皂吮,像睡著了一般。 火紅的嫁衣襯著肌膚如雪税手。 梳的紋絲不亂的頭發(fā)上蜂筹,一...
    開封第一講書人閱讀 52,736評論 1 312
  • 那天,我揣著相機(jī)與錄音芦倒,去河邊找鬼艺挪。 笑死,一個胖子當(dāng)著我的面吹牛兵扬,可吹牛的內(nèi)容都是我干的麻裳。 我是一名探鬼主播,決...
    沈念sama閱讀 41,179評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼器钟,長吁一口氣:“原來是場噩夢啊……” “哼津坑!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起傲霸,我...
    開封第一講書人閱讀 40,124評論 0 277
  • 序言:老撾萬榮一對情侶失蹤疆瑰,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后狞谱,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體乃摹,經(jīng)...
    沈念sama閱讀 46,657評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,723評論 3 342
  • 正文 我和宋清朗相戀三年跟衅,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片播歼。...
    茶點故事閱讀 40,872評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡伶跷,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出秘狞,到底是詐尸還是另有隱情叭莫,我是刑警寧澤,帶...
    沈念sama閱讀 36,533評論 5 351
  • 正文 年R本政府宣布烁试,位于F島的核電站雇初,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏减响。R本人自食惡果不足惜靖诗,卻給世界環(huán)境...
    茶點故事閱讀 42,213評論 3 336
  • 文/蒙蒙 一郭怪、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧刊橘,春花似錦鄙才、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,700評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至败晴,卻和暖如春浓冒,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背尖坤。 一陣腳步聲響...
    開封第一講書人閱讀 33,819評論 1 274
  • 我被黑心中介騙來泰國打工稳懒, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人糖驴。 一個月前我還...
    沈念sama閱讀 49,304評論 3 379
  • 正文 我出身青樓僚祷,卻偏偏與公主長得像,于是被迫代替她去往敵國和親贮缕。 傳聞我的和親對象是個殘疾皇子辙谜,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,876評論 2 361

推薦閱讀更多精彩內(nèi)容