日本不卡不码高清免费观看,久久国产精品久久w女人spa,黄色aa久久,三上悠亚国产精品一区二区三区

您的位置:首頁(yè)技術(shù)文章
文章詳情頁(yè)

java agent使用全解析

瀏覽:15日期:2022-08-30 08:06:55

今天打算寫一下 Java agent,一開始我對(duì)它的概念也比較陌生,后來在別人口中聽到 字節(jié)碼插樁,bTrace,Arthas后面才逐漸了解到Java還提供了這么個(gè)工具。

JVM啟動(dòng)前靜態(tài)Instrument

Java agent 是什么?

Java agent是java命令的一個(gè)參數(shù)。參數(shù) javaagent 可以用于指定一個(gè) jar 包,并且對(duì)該 java 包有2個(gè)要求:

這個(gè) jar 包的 MANIFEST.MF 文件必須指定 Premain-Class 項(xiàng)。 Premain-Class 指定的那個(gè)類必須實(shí)現(xiàn) premain() 方法。

premain 方法,從字面上理解,就是運(yùn)行在 main 函數(shù)之前的的類。當(dāng)Java 虛擬機(jī)啟動(dòng)時(shí),在執(zhí)行 main 函數(shù)之前,JVM 會(huì)先運(yùn)行-javaagent所指定 jar 包內(nèi) Premain-Class 這個(gè)類的 premain 方法 。

在命令行輸入 java可以看到相應(yīng)的參數(shù),其中有 和 java agent相關(guān)的:

-agentlib:<libname>[=<選項(xiàng)>] 加載本機(jī)代理庫(kù) <libname>, 例如 -agentlib:hprof 另請(qǐng)參閱 -agentlib:jdwp=help 和 -agentlib:hprof=help-agentpath:<pathname>[=<選項(xiàng)>] 按完整路徑名加載本機(jī)代理庫(kù)-javaagent:<jarpath>[=<選項(xiàng)>] 加載 Java 編程語言代理, 請(qǐng)參閱 java.lang.instrument

在上面-javaagent參數(shù)中提到了參閱java.lang.instrument,這是在rt.jar 中定義的一個(gè)包,該路徑下有兩個(gè)重要的類:

java agent使用全解析

該包提供了一些工具幫助開發(fā)人員在 Java 程序運(yùn)行時(shí),動(dòng)態(tài)修改系統(tǒng)中的 Class 類型。其中,使用該軟件包的一個(gè)關(guān)鍵組件就是 Javaagent。從名字上看,似乎是個(gè) Java 代理之類的,而實(shí)際上,他的功能更像是一個(gè)Class 類型的轉(zhuǎn)換器,他可以在運(yùn)行時(shí)接受重新外部請(qǐng)求,對(duì)Class類型進(jìn)行修改。

從本質(zhì)上講,Java Agent 是一個(gè)遵循一組嚴(yán)格約定的常規(guī) Java 類。 上面說到 javaagent命令要求指定的類中必須要有premain()方法,并且對(duì)premain方法的簽名也有要求,簽名必須滿足以下兩種格式:

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

JVM 會(huì)優(yōu)先加載 帶 Instrumentation 簽名的方法,加載成功忽略第二種,如果第一種沒有,則加載第二種方法。這個(gè)邏輯在sun.instrument.InstrumentationImpl 類中:

java agent使用全解析

Instrumentation 類 定義如下:

public interface Instrumentation { //增加一個(gè)Class 文件的轉(zhuǎn)換器,轉(zhuǎn)換器用于改變 Class 二進(jìn)制流的數(shù)據(jù),參數(shù) canRetransform 設(shè)置是否允許重新轉(zhuǎn)換。 void addTransformer(ClassFileTransformer transformer, boolean canRetransform); //在類加載之前,重新定義 Class 文件,ClassDefinition 表示對(duì)一個(gè)類新的定義,如果在類加載之后,需要使用 retransformClasses 方法重新定義。addTransformer方法配置之后,后續(xù)的類加載都會(huì)被Transformer攔截。對(duì)于已經(jīng)加載過的類,可以執(zhí)行retransformClasses來重新觸發(fā)這個(gè)Transformer的攔截。類加載的字節(jié)碼被修改后,除非再次被retransform,否則不會(huì)恢復(fù)。 void addTransformer(ClassFileTransformer transformer); //刪除一個(gè)類轉(zhuǎn)換器 boolean removeTransformer(ClassFileTransformer transformer); boolean isRetransformClassesSupported(); //在類加載之后,重新定義 Class。這個(gè)很重要,該方法是1.6 之后加入的,事實(shí)上,該方法是 update 了一個(gè)類。 void retransformClasses(Class<?>... classes) throws UnmodifiableClassException; boolean isRedefineClassesSupported(); void redefineClasses(ClassDefinition... definitions) throws ClassNotFoundException, UnmodifiableClassException; boolean isModifiableClass(Class<?> theClass); @SuppressWarnings('rawtypes') Class[] getAllLoadedClasses(); @SuppressWarnings('rawtypes') Class[] getInitiatedClasses(ClassLoader loader); //獲取一個(gè)對(duì)象的大小 long getObjectSize(Object objectToSize); void appendToBootstrapClassLoaderSearch(JarFile jarfile); void appendToSystemClassLoaderSearch(JarFile jarfile); boolean isNativeMethodPrefixSupported(); void setNativeMethodPrefix(ClassFileTransformer transformer, String prefix);}

最為重要的是上面注釋的幾個(gè)方法,下面我們會(huì)用到。

如何使用javaagent?

使用 javaagent 需要幾個(gè)步驟:

定義一個(gè) MANIFEST.MF 文件,必須包含 Premain-Class 選項(xiàng),通常也會(huì)加入Can-Redefine-Classes 和 Can-Retransform-Classes 選項(xiàng)。 創(chuàng)建一個(gè)Premain-Class 指定的類,類中包含 premain 方法,方法邏輯由用戶自己確定。 將 premain 的類和 MANIFEST.MF 文件打成 jar 包。 使用參數(shù) -javaagent: jar包路徑 啟動(dòng)要代理的方法。

在執(zhí)行以上步驟后,JVM 會(huì)先執(zhí)行 premain 方法,大部分類加載都會(huì)通過該方法,注意:是大部分,不是所有。當(dāng)然,遺漏的主要是系統(tǒng)類,因?yàn)楹芏嘞到y(tǒng)類先于 agent 執(zhí)行,而用戶類的加載肯定是會(huì)被攔截的。也就是說,這個(gè)方法是在 main 方法啟動(dòng)前攔截大部分類的加載活動(dòng),既然可以攔截類的加載,那么就可以去做重寫類這樣的操作,結(jié)合第三方的字節(jié)碼編譯工具,比如ASM,javassist,cglib等等來改寫實(shí)現(xiàn)類。

通過上面的步驟我們用代碼實(shí)現(xiàn)來實(shí)現(xiàn)。實(shí)現(xiàn) javaagent 你需要搭建兩個(gè)工程,一個(gè)工程是用來承載 javaagent類,單獨(dú)的打成jar包;一個(gè)工程是javaagent需要去代理的類。即javaagent會(huì)在這個(gè)工程中的main方法啟動(dòng)之前去做一些事情。

1.首先來實(shí)現(xiàn)javaagent工程。

工程目錄結(jié)構(gòu)如下:

-java-agent----src--------main--------|------java--------|----------com.rickiyang.learn--------|------------PreMainTraceAgent--------|resources-----------META-INF--------------MANIFEST.MF

第一步是需要?jiǎng)?chuàng)建一個(gè)類,包含premain 方法:

import java.lang.instrument.ClassFileTransformer;import java.lang.instrument.IllegalClassFormatException;import java.lang.instrument.Instrumentation;import java.security.ProtectionDomain;/** * @author: rickiyang * @date: 2019/8/12 * @description: */public class PreMainTraceAgent { public static void premain(String agentArgs, Instrumentation inst) { System.out.println('agentArgs : ' + agentArgs); inst.addTransformer(new DefineTransformer(), true); } static class DefineTransformer implements ClassFileTransformer{ @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { System.out.println('premain load Class:' + className); return classfileBuffer; } }}

上面就是我實(shí)現(xiàn)的一個(gè)類,實(shí)現(xiàn)了帶Instrumentation參數(shù)的premain()方法。調(diào)用addTransformer()方法對(duì)啟動(dòng)時(shí)所有的類進(jìn)行攔截。

然后在 resources 目錄下新建目錄:META-INF,在該目錄下新建文件:MANIFREST.MF:

Manifest-Version: 1.0Can-Redefine-Classes: trueCan-Retransform-Classes: truePremain-Class: PreMainTraceAgent

注意到第5行有空行。

說一下MANIFREST.MF文件的作用,這里如果你不去手動(dòng)指定的話,直接 打包,默認(rèn)會(huì)在打包的文件中生成一個(gè)MANIFREST.MF文件:

Manifest-Version: 1.0Implementation-Title: test-agentImplementation-Version: 0.0.1-SNAPSHOTBuilt-By: yangyueImplementation-Vendor-Id: com.rickiyang.learnSpring-Boot-Version: 2.0.9.RELEASEMain-Class: org.springframework.boot.loader.JarLauncherStart-Class: com.rickiyang.learn.LearnApplicationSpring-Boot-Classes: BOOT-INF/classes/Spring-Boot-Lib: BOOT-INF/lib/Created-By: Apache Maven 3.5.2Build-Jdk: 1.8.0_151Implementation-URL: https://projects.spring.io/spring-boot/#/spring-bo ot-starter-parent/test-agent

這是默認(rèn)的文件,包含當(dāng)前的一些版本信息,當(dāng)前工程的啟動(dòng)類,它還有別的參數(shù)允許你做更多的事情,可以用上的有:

Premain-Class :包含 premain 方法的類(類的全路徑名) Agent-Class :包含 agentmain 方法的類(類的全路徑名) Boot-Class-Path :設(shè)置引導(dǎo)類加載器搜索的路徑列表。查找類的特定于平臺(tái)的機(jī)制失敗后,引導(dǎo)類加載器會(huì)搜索這些路徑。按列出的順序搜索路徑。列表中的路徑由一個(gè)或多個(gè)空格分開。路徑使用分層 URI 的路徑組件語法。如果該路徑以斜杠字符(“/”)開頭,則為絕對(duì)路徑,否則為相對(duì)路徑。相對(duì)路徑根據(jù)代理 JAR 文件的絕對(duì)路徑解析。忽略格式不正確的路徑和不存在的路徑。如果代理是在 VM 啟動(dòng)之后某一時(shí)刻啟動(dòng)的,則忽略不表示 JAR 文件的路徑。(可選) Can-Redefine-Classes :true表示能重定義此代理所需的類,默認(rèn)值為 false(可選) Can-Retransform-Classes :true 表示能重轉(zhuǎn)換此代理所需的類,默認(rèn)值為 false (可選) Can-Set-Native-Method-Prefix: true表示能設(shè)置此代理所需的本機(jī)方法前綴,默認(rèn)值為 false(可選)

即在該文件中主要定義了程序運(yùn)行相關(guān)的配置信息,程序運(yùn)行前會(huì)先檢測(cè)該文件中的配置項(xiàng)。

一個(gè)java程序中-javaagent參數(shù)的個(gè)數(shù)是沒有限制的,所以可以添加任意多個(gè)javaagent。所有的java agent會(huì)按照你定義的順序執(zhí)行,例如:

java -javaagent:agent1.jar -javaagent:agent2.jar -jar MyProgram.jar

程序執(zhí)行的順序?qū)?huì)是:

MyAgent1.premain -> MyAgent2.premain -> MyProgram.main

說回上面的 javaagent工程,接下來將該工程打成jar包,我在打包的時(shí)候發(fā)現(xiàn)打完包之后的 MANIFREST.MF文件被默認(rèn)配置替換掉了。所以我是手動(dòng)將上面我的配置文件替換到j(luò)ar包中的文件,這里你需要注意。

另外的再說一種不去手動(dòng)寫MANIFREST.MF文件的方式,使用maven插件:

<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>3.1.0</version> <configuration> <archive> <!--自動(dòng)添加META-INF/MANIFEST.MF --> <manifest><addClasspath>true</addClasspath> </manifest> <manifestEntries><Premain-Class>com.rickiyang.learn.PreMainTraceAgent</Premain-Class><Agent-Class>com.rickiyang.learn.PreMainTraceAgent</Agent-Class><Can-Redefine-Classes>true</Can-Redefine-Classes><Can-Retransform-Classes>true</Can-Retransform-Classes> </manifestEntries> </archive> </configuration></plugin>

用這種插件的方式也可以自動(dòng)生成該文件。

agent代碼就寫完了,下面再重新開一個(gè)工程,你只需要寫一個(gè)帶 main 方法的類即可:

public class TestMain { public static void main(String[] args) { System.out.println('main start'); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println('main end'); }}

很簡(jiǎn)單,然后需要做的就是將上面的 代理類 和 這個(gè)測(cè)試類關(guān)聯(lián)起來。有兩種方式:

如果你用的是idea,那么你可以點(diǎn)擊菜單: run-debug configuration,然后將你的代理類包 指定在 啟動(dòng)參數(shù)中即可:

java agent使用全解析

另一種方式是不用 編譯器,采用命令行的方法。與上面大致相同,將 上面的測(cè)試類編譯成 class文件,然后 運(yùn)行該類即可:

#將該類編譯成class文件 > javac TestMain.java #指定agent程序并運(yùn)行該類 > java -javaagent:c:/alg.jar TestMain

使用上面兩種方式都可以運(yùn)行,輸出結(jié)果如下:

D:softjdk1.8binjava.exe -javaagent:c:/alg.jar '-javaagent:D:softIntelliJ IDEA 2019.1.1libidea_rt.jar=54274:D:softIntelliJ IDEA 2019.1.1bin' -Dfile.encoding=UTF-8 -classpath D:softjdk1.8jrelibcharsets.jar;D:softjdk1.8jrelibdeploy.jar;D:softjdk1.8jrelibextaccess-bridge-64.jar;D:softjdk1.8jrelibextcldrdata.jar;D:softjdk1.8jrelibextdnsns.jar;D:softjdk1.8jrelibextjaccess.jar;D:softjdk1.8jrelibextjfxrt.jar;D:softjdk1.8jrelibextlocaledata.jar;D:softjdk1.8jrelibextnashorn.jar;D:softjdk1.8jrelibextsunec.jar;D:softjdk1.8jrelibextsunjce_provider.jar;D:softjdk1.8jrelibextsunmscapi.jar;D:softjdk1.8jrelibextsunpkcs11.jar;D:softjdk1.8jrelibextzipfs.jar;D:softjdk1.8jrelibjavaws.jar;D:softjdk1.8jrelibjce.jar;D:softjdk1.8jrelibjfr.jar;D:softjdk1.8jrelibjfxswt.jar;D:softjdk1.8jrelibjsse.jar;D:softjdk1.8jrelibmanagement-agent.jar;D:softjdk1.8jrelibplugin.jar;D:softjdk1.8jrelibresources.jar;D:softjdk1.8jrelibrt.jar;D:workspacedemo1targetclasses;E:.m2repositoryorgspringframeworkbootspring-boot-starter-aop2.1.1.RELEASEspring-.........1.8.11.jar;E:.m2repositorycomgoogleguavaguava20.0guava-20.0.jar;E:.m2repositoryorgapachecommonscommons-lang33.7commons-lang3-3.7.jar;E:.m2repositorycomalibabafastjson1.2.54fastjson-1.2.54.jar;E:.m2repositoryorgspringframeworkbootspring-boot2.1.0.RELEASEspring-boot-2.1.0.RELEASE.jar;E:.m2repositoryorgspringframeworkspring-context5.1.3.RELEASEspring-context-5.1.3.RELEASE.jar com.springboot.example.demo.service.TestMainagentArgs : nullpremain load Class :java/util/concurrent/ConcurrentHashMap$ForwardingNodepremain load Class :sun/nio/cs/ThreadLocalCoderspremain load Class :sun/nio/cs/ThreadLocalCoders$1premain load Class :sun/nio/cs/ThreadLocalCoders$Cachepremain load Class :sun/nio/cs/ThreadLocalCoders$2premain load Class :java/util/jar/Attributespremain load Class :java/util/jar/Manifest$FastInputStream.........premain load Class :java/lang/Class$MethodArraypremain load Class :java/lang/Voidmain startpremain load Class :sun/misc/VMSupportpremain load Class :java/util/Hashtable$KeySetpremain load Class :sun/nio/cs/ISO_8859_1$Encoderpremain load Class :sun/nio/cs/Surrogate$Parserpremain load Class :sun/nio/cs/Surrogate.........premain load Class :sun/util/locale/provider/LocaleResources$ResourceReferencemain endpremain load Class :java/lang/Shutdownpremain load Class :java/lang/Shutdown$Lock

Process finished with exit code 0

上面的輸出結(jié)果我們能夠發(fā)現(xiàn):

執(zhí)行main方法之前會(huì)加載所有的類,包括系統(tǒng)類和自定義類; 在ClassFileTransformer中會(huì)去攔截系統(tǒng)類和自己實(shí)現(xiàn)的類對(duì)象; 如果你有對(duì)某些類對(duì)象進(jìn)行改寫,那么在攔截的時(shí)候抓住該類使用字節(jié)碼編譯工具即可實(shí)現(xiàn)。

下面是使用javassist來動(dòng)態(tài)將某個(gè)方法替換掉:

package com.rickiyang.learn;import javassist.*;import java.io.IOException;import java.lang.instrument.ClassFileTransformer;import java.security.ProtectionDomain;/** * @author rickiyang * @date 2019-08-06 * @Desc */public class MyClassTransformer implements ClassFileTransformer { @Override public byte[] transform(final ClassLoader loader, final String className, final Class<?> classBeingRedefined,final ProtectionDomain protectionDomain, final byte[] classfileBuffer) { // 操作Date類 if ('java/util/Date'.equals(className)) { try {// 從ClassPool獲得CtClass對(duì)象final ClassPool classPool = ClassPool.getDefault();final CtClass clazz = classPool.get('java.util.Date');CtMethod convertToAbbr = clazz.getDeclaredMethod('convertToAbbr');//這里對(duì) java.util.Date.convertToAbbr() 方法進(jìn)行了改寫,在 return之前增加了一個(gè) 打印操作String methodBody = '{sb.append(Character.toUpperCase(name.charAt(0)));' + 'sb.append(name.charAt(1)).append(name.charAt(2));' + 'System.out.println('sb.toString()');' + 'return sb;}';convertToAbbr.setBody(methodBody);// 返回字節(jié)碼,并且detachCtClass對(duì)象byte[] byteCode = clazz.toBytecode();//detach的意思是將內(nèi)存中曾經(jīng)被javassist加載過的Date對(duì)象移除,如果下次有需要在內(nèi)存中找不到會(huì)重新走javassist加載clazz.detach();return byteCode; } catch (Exception ex) {ex.printStackTrace(); } } // 如果返回null則字節(jié)碼不會(huì)被修改 return null; }}

JVM啟動(dòng)后動(dòng)態(tài)Instrument

上面介紹的Instrumentation是在 JDK 1.5中提供的,開發(fā)者只能在main加載之前添加手腳,在 Java SE 6 的 Instrumentation 當(dāng)中,提供了一個(gè)新的代理操作方法:agentmain,可以在 main 函數(shù)開始運(yùn)行之后再運(yùn)行。

跟premain函數(shù)一樣, 開發(fā)者可以編寫一個(gè)含有agentmain函數(shù)的 Java 類:

//采用attach機(jī)制,被代理的目標(biāo)程序VM有可能很早之前已經(jīng)啟動(dòng),當(dāng)然其所有類已經(jīng)被加載完成,這個(gè)時(shí)候需要借助Instrumentation#retransformClasses(Class<?>... classes)讓對(duì)應(yīng)的類可以重新轉(zhuǎn)換,從而激活重新轉(zhuǎn)換的類執(zhí)行ClassFileTransformer列表中的回調(diào)public static void agentmain (String agentArgs, Instrumentation inst)public static void agentmain (String agentArgs)

同樣,agentmain 方法中帶Instrumentation參數(shù)的方法也比不帶優(yōu)先級(jí)更高。開發(fā)者必須在 manifest 文件里面設(shè)置“Agent-Class”來指定包含 agentmain 函數(shù)的類。

在Java6 以后實(shí)現(xiàn)啟動(dòng)后加載的新實(shí)現(xiàn)是Attach api。Attach API 很簡(jiǎn)單,只有 2 個(gè)主要的類,都在 com.sun.tools.attach 包里面:

java agent使用全解析

VirtualMachine 字面意義表示一個(gè)Java 虛擬機(jī),也就是程序需要監(jiān)控的目標(biāo)虛擬機(jī),提供了獲取系統(tǒng)信息(比如獲取內(nèi)存dump、線程dump,類信息統(tǒng)計(jì)(比如已加載的類以及實(shí)例個(gè)數(shù)等), loadAgent,Attach 和 Detach (Attach 動(dòng)作的相反行為,從 JVM 上面解除一個(gè)代理)等方法,可以實(shí)現(xiàn)的功能可以說非常之強(qiáng)大 。該類允許我們通過給attach方法傳入一個(gè)jvm的pid(進(jìn)程id),遠(yuǎn)程連接到j(luò)vm上 。代理類注入操作只是它眾多功能中的一個(gè),通過loadAgent方法向jvm注冊(cè)一個(gè)代理程序agent,在該agent的代理程序中會(huì)得到一個(gè)Instrumentation實(shí)例,該實(shí)例可以 在class加載前改變class的字節(jié)碼,也可以在class加載后重新加載。在調(diào)用Instrumentation實(shí)例的方法時(shí),這些方法會(huì)使用ClassFileTransformer接口中提供的方法進(jìn)行處理。 VirtualMachineDescriptor 則是一個(gè)描述虛擬機(jī)的容器類,配合 VirtualMachine 類完成各種功能。

attach實(shí)現(xiàn)動(dòng)態(tài)注入的原理如下:

通過VirtualMachine類的attach(pid)方法,便可以attach到一個(gè)運(yùn)行中的java進(jìn)程上,之后便可以通過loadAgent(agentJarPath)來將agent的jar包注入到對(duì)應(yīng)的進(jìn)程,然后對(duì)應(yīng)的進(jìn)程會(huì)調(diào)用agentmain方法。

java agent使用全解析

既然是兩個(gè)進(jìn)程之間通信那肯定的建立起連接,VirtualMachine.attach動(dòng)作類似TCP創(chuàng)建連接的三次握手,目的就是搭建attach通信的連接。而后面執(zhí)行的操作,例如vm.loadAgent,其實(shí)就是向這個(gè)socket寫入數(shù)據(jù)流,接收方target VM會(huì)針對(duì)不同的傳入數(shù)據(jù)來做不同的處理。

我們來測(cè)試一下agentmain的使用:

工程結(jié)構(gòu)和 上面premain的測(cè)試一樣,編寫AgentMainTest,然后使用maven插件打包 生成MANIFEST.MF。

package com.rickiyang.learn;import java.lang.instrument.ClassFileTransformer;import java.lang.instrument.IllegalClassFormatException;import java.lang.instrument.Instrumentation;import java.security.ProtectionDomain;/** * @author rickiyang * @date 2019-08-16 * @Desc */public class AgentMainTest { public static void agentmain(String agentArgs, Instrumentation instrumentation) { instrumentation.addTransformer(new DefineTransformer(), true); } static class DefineTransformer implements ClassFileTransformer { @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { System.out.println('premain load Class:' + className); return classfileBuffer; } }}

<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>3.1.0</version> <configuration> <archive> <!--自動(dòng)添加META-INF/MANIFEST.MF --> <manifest> <addClasspath>true</addClasspath> </manifest> <manifestEntries> <Agent-Class>com.rickiyang.learn.AgentMainTest</Agent-Class> <Can-Redefine-Classes>true</Can-Redefine-Classes> <Can-Retransform-Classes>true</Can-Retransform-Classes> </manifestEntries> </archive> </configuration></plugin>

將agent打包之后,就是編寫測(cè)試main方法。上面我們畫的圖中的步驟是:從一個(gè)attach JVM去探測(cè)目標(biāo)JVM,如果目標(biāo)JVM存在則向它發(fā)送agent.jar。我測(cè)試寫的簡(jiǎn)單了些,找到當(dāng)前JVM并加載agent.jar。

package com.rickiyang.learn.job;import com.sun.tools.attach.*;import java.io.IOException;import java.util.List;/** * @author rickiyang * @date 2019-08-16 * @Desc */public class TestAgentMain { public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException { //獲取當(dāng)前系統(tǒng)中所有 運(yùn)行中的 虛擬機(jī) System.out.println('running JVM start '); List<VirtualMachineDescriptor> list = VirtualMachine.list(); for (VirtualMachineDescriptor vmd : list) { //如果虛擬機(jī)的名稱為 xxx 則 該虛擬機(jī)為目標(biāo)虛擬機(jī),獲取該虛擬機(jī)的 pid //然后加載 agent.jar 發(fā)送給該虛擬機(jī) System.out.println(vmd.displayName()); if (vmd.displayName().endsWith('com.rickiyang.learn.job.TestAgentMain')) {VirtualMachine virtualMachine = VirtualMachine.attach(vmd.id());virtualMachine.loadAgent('/Users/yangyue/Documents/java-agent.jar');virtualMachine.detach(); } } }}

list()方法會(huì)去尋找當(dāng)前系統(tǒng)中所有運(yùn)行著的JVM進(jìn)程,你可以打印vmd.displayName()看到當(dāng)前系統(tǒng)都有哪些JVM進(jìn)程在運(yùn)行。因?yàn)閙ain函數(shù)執(zhí)行起來的時(shí)候進(jìn)程名為當(dāng)前類名,所以通過這種方式可以去找到當(dāng)前的進(jìn)程id。

注意:在mac上安裝了的jdk是能直接找到 VirtualMachine 類的,但是在windows中安裝的jdk無法找到,如果你遇到這種情況,請(qǐng)手動(dòng)將你jdk安裝目錄下:lib目錄中的tools.jar添加進(jìn)當(dāng)前工程的Libraries中。

運(yùn)行main方法的輸出為:

java agent使用全解析

可以看到實(shí)際上是啟動(dòng)了一個(gè)socket進(jìn)程去傳輸agent.jar。先打印了“running JVM start”表名main方法是先啟動(dòng)了,然后才進(jìn)入代理類的transform方法。

instrument原理

instrument的底層實(shí)現(xiàn)依賴于JVMTI(JVM Tool Interface),它是JVM暴露出來的一些供用戶擴(kuò)展的接口集合,JVMTI是基于事件驅(qū)動(dòng)的,JVM每執(zhí)行到一定的邏輯就會(huì)調(diào)用一些事件的回調(diào)接口(如果有的話),這些接口可以供開發(fā)者去擴(kuò)展自己的邏輯。JVMTIAgent是一個(gè)利用JVMTI暴露出來的接口提供了代理啟動(dòng)時(shí)加載(agent on load)、代理通過attach形式加載(agent on attach)和代理卸載(agent on unload)功能的動(dòng)態(tài)庫(kù)。而instrument agent可以理解為一類JVMTIAgent動(dòng)態(tài)庫(kù),別名是JPLISAgent(Java Programming Language Instrumentation Services Agent),也就是專門為java語言編寫的插樁服務(wù)提供支持的代理。

啟動(dòng)時(shí)加載instrument agent過程:

1.創(chuàng)建并初始化 JPLISAgent;

2.監(jiān)聽 VMInit 事件,在 JVM 初始化完成之后做下面的事情:

創(chuàng)建 InstrumentationImpl 對(duì)象 ; 監(jiān)聽 ClassFileLoadHook 事件 ; 調(diào)用 InstrumentationImpl 的loadClassAndCallPremain方法,在這個(gè)方法里會(huì)去調(diào)用 javaagent 中 MANIFEST.MF 里指定的Premain-Class 類的 premain 方法 ;

3.解析 javaagent 中 MANIFEST.MF 文件的參數(shù),并根據(jù)這些參數(shù)來設(shè)置 JPLISAgent 里的一些內(nèi)容。

運(yùn)行時(shí)加載instrument agent過程:

通過 JVM 的attach機(jī)制來請(qǐng)求目標(biāo) JVM 加載對(duì)應(yīng)的agent,過程大致如下:

1.創(chuàng)建并初始化JPLISAgent;

2.解析 javaagent 里 MANIFEST.MF 里的參數(shù);

3.創(chuàng)建 InstrumentationImpl 對(duì)象;

4.監(jiān)聽 ClassFileLoadHook 事件;

5.調(diào)用 InstrumentationImpl 的loadClassAndCallAgentmain方法,在這個(gè)方法里會(huì)去調(diào)用javaagent里 MANIFEST.MF 里指定的Agent-Class類的agentmain方法。

Instrumentation的局限性

大多數(shù)情況下,我們使用Instrumentation都是使用其字節(jié)碼插樁的功能,或者籠統(tǒng)說就是類重定義(Class Redefine)的功能,但是有以下的局限性:

1.premain和agentmain兩種方式修改字節(jié)碼的時(shí)機(jī)都是類文件加載之后,也就是說必須要帶有Class類型的參數(shù),不能通過字節(jié)碼文件和自定義的類名重新定義一個(gè)本來不存在的類。

2.類的字節(jié)碼修改稱為類轉(zhuǎn)換(Class Transform),類轉(zhuǎn)換其實(shí)最終都回歸到類重定義Instrumentation#redefineClasses()方法,此方法有以下限制:

新類和老類的父類必須相同; 新類和老類實(shí)現(xiàn)的接口數(shù)也要相同,并且是相同的接口; 新類和老類訪問符必須一致。 新類和老類字段數(shù)和字段名要一致; 新類和老類新增或刪除的方法必須是private static/final修飾的; 可以修改方法體。

除了上面的方式,如果想要重新定義一個(gè)類,可以考慮基于類加載器隔離的方式:創(chuàng)建一個(gè)新的自定義類加載器去通過新的字節(jié)碼去定義一個(gè)全新的類,不過也存在只能通過反射調(diào)用該全新類的局限性。

以上就是javaagent使用全解析的詳細(xì)內(nèi)容,更多關(guān)于javaagent 使用的資料請(qǐng)關(guān)注好吧啦網(wǎng)其它相關(guān)文章!

標(biāo)簽: Java
相關(guān)文章:
日本不卡不码高清免费观看,久久国产精品久久w女人spa,黄色aa久久,三上悠亚国产精品一区二区三区
bbw在线视频| 国产欧美综合一区二区三区| 牛牛精品成人免费视频| 里番精品3d一二三区| 国产精品最新| 日韩av在线播放中文字幕| 日本a口亚洲| 精品久久久中文字幕| 91亚洲一区| 日韩一区欧美| 日本va欧美va精品发布| 亚洲免费福利| 成人在线免费观看网站| 日韩中出av| 亚洲激情社区| 91精品精品| 国产精品一区二区三区四区在线观看| 国产精品白丝av嫩草影院| 欧美+日本+国产+在线a∨观看| 国产精品视频一区二区三区| 喷白浆一区二区| 黄色国产精品| 国产亚洲欧美日韩精品一区二区三区 | 视频一区中文字幕| 亚洲午夜久久久久久尤物| 日韩不卡视频在线观看| 日韩电影二区| 久久亚洲美女| 精品网站aaa| 国产免费播放一区二区| 人人草在线视频| 国产精品久一| 亚洲欧美不卡| 日韩国产欧美视频| 国产精品腿扒开做爽爽爽挤奶网站| 成人精品久久| 欧美日韩水蜜桃| 国产精品一线天粉嫩av| 欧美亚洲自偷自偷| 最近高清中文在线字幕在线观看1| 亚洲综合福利| 欧美xxxx中国| 欧美激情精品| 欧美精品一卡| 久久99性xxx老妇胖精品| 精品久久在线| 免费日韩av片| 在线精品视频一区| 日韩精品久久久久久久电影99爱| 亚洲作爱视频| 欧美精品三级在线| 国产欧美日本| 一本色道久久精品| 精品国产不卡一区二区| 亚洲精品在线影院| 国产精品一区二区三区四区在线观看 | 日本午夜精品一区二区三区电影| 国产精品一区二区精品视频观看| 四季av一区二区凹凸精品| 三级久久三级久久久| 免费不卡中文字幕在线| 欧美一区二区三区高清视频| 少妇精品导航| jizzjizz中国精品麻豆| 国产成人免费| 超碰在线99| 久久久久伊人| 久久久男人天堂| 国产精品毛片aⅴ一区二区三区| 亚洲精品字幕| 日韩1区2区日韩1区2区| 日韩成人av影视| 欧美黄色一区二区| 久久高清精品| 日韩视频二区| 亚洲播播91| 婷婷成人在线| 国产日产精品_国产精品毛片 | 欧美日韩夜夜| 一二三区精品| 久久丁香四色| 香蕉成人久久| 美腿丝袜亚洲三区| 亚洲精品中文字幕乱码| 国产麻豆一区二区三区精品视频| 九九精品调教| 韩国女主播一区二区三区| 国产精品国产三级在线观看| 99精品美女| 99精品美女| 欧美欧美黄在线二区| 久久精品导航| 婷婷成人av| 精品久久中文| 亚洲精品影院在线观看| 亚洲永久字幕| 亚洲中午字幕| 99热精品在线观看| 亚洲激情不卡| 亚洲成人一区| 精品网站999| 精品国内亚洲2022精品成人| 亚洲精品国产精品粉嫩| 高清av一区| 国产精品porn| 精品国产一区二区三区噜噜噜| 午夜在线精品| 欧美日韩中文一区二区| 日韩大片在线播放| 欧美日韩99| 久久久噜噜噜| 亚洲风情在线资源| 色天使综合视频| 亚洲v天堂v手机在线| 日本亚洲最大的色成网站www| 久久xxx视频| 亚洲综合精品四区| 亚洲色图国产| 国产精品97| 欧美.日韩.国产.一区.二区| 久久av中文| 久久高清免费| 亚洲一区国产| 成人av动漫在线观看| 欧美国产免费| 国产欧美一区二区三区精品观看| 美女日韩在线中文字幕| 日韩中文字幕亚洲一区二区va在线 | 午夜亚洲一区| 一本综合精品| 精品视频一区二区三区在线观看 | 国产精品麻豆久久| 国产精品试看| 91精品在线免费视频| 成年男女免费视频网站不卡| 一区二区三区四区在线观看国产日韩| 青青草国产精品亚洲专区无| 国产一区福利| 日本а中文在线天堂| 精品欧美一区二区三区在线观看| 亚洲国产成人精品女人| 国产精品久久久久久久久久10秀| 激情久久99| 国产不卡人人| 国产精品av一区二区| 国产精品视频首页| 国产成人免费精品| 欧美午夜精品一区二区三区电影| 日本不卡高清视频| 国产日韩免费| 国产精品国码视频| 日本一区二区三区中文字幕| 欧美日韩夜夜| 国产在线日韩精品| 视频精品一区二区| 午夜久久影院| 亚洲二区在线| 国产精品一卡| 香蕉久久久久久久av网站| 美女视频黄 久久| 日韩在线精品| 国产伦精品一区二区三区千人斩| 亚洲精品乱码久久久久久蜜桃麻豆| 久久久噜噜噜| 在线看片国产福利你懂的| 亚洲精品黄色| 亚洲一级大片| 日韩精品电影| 国产日韩三级| 偷拍欧美精品| 水蜜桃久久夜色精品一区| 精品国产精品国产偷麻豆 | 激情91久久| 久久人人精品| 精品欧美久久| 亚洲在线网站| 午夜久久中文| 国产美女高潮在线观看| 久久亚洲精品中文字幕蜜潮电影| 欧美亚洲国产激情| 久草免费在线视频| 韩国女主播一区二区三区| 99国产精品久久久久久久成人热| 国产成人免费av一区二区午夜| 精品国产aⅴ| 911精品国产| 日韩精品免费视频一区二区三区| 美女国产一区二区三区| 国产精品毛片aⅴ一区二区三区| 国产精品99视频| 黑森林国产精品av| 免费成人av在线播放| 国产乱码精品| 亚洲精品1区| 亚洲精品88| 色偷偷偷在线视频播放| 国产精品天天看天天狠| 狂野欧美性猛交xxxx| 精品丝袜久久| 丝袜亚洲精品中文字幕一区|