崩潰了!我說用attach進(jìn)行問題定位,面試官問我原理

Arthas(阿爾薩斯)是一款開源的Java診斷和監(jiān)控工具,可以在生產(chǎn)環(huán)境中進(jìn)行實(shí)時(shí)的應(yīng)用程序分析和故障排查。Arthas的實(shí)現(xiàn)原理主要基于Java Instrumentation API和Java Agent技術(shù)。

Java Agent 是 Java 編程語言提供的一種特殊機(jī)制,允許你在程序運(yùn)行過程中對(duì)字節(jié)碼進(jìn)行轉(zhuǎn)換和增強(qiáng)。它是通過 Java 的 Instrumentation API 來實(shí)現(xiàn)的,可以用于在應(yīng)用程序加載類時(shí)進(jìn)行監(jiān)測(cè)、修改和增強(qiáng)。Java Agent 通常被用于實(shí)現(xiàn)性能監(jiān)測(cè)、代碼分析、方法耗時(shí)統(tǒng)計(jì)、字節(jié)碼增強(qiáng)等功能。

Java Agent 的主要特點(diǎn)

  1. 動(dòng)態(tài)性: Java Agent 允許你在程序運(yùn)行時(shí)加載代理類,對(duì)類進(jìn)行轉(zhuǎn)換和增強(qiáng),從而實(shí)現(xiàn)動(dòng)態(tài)修改已編譯類的功能。

  2. 無侵入性: 使用 Java Agent 不需要修改源代碼,也不需要重新編譯。這使得你可以在不改變程序結(jié)構(gòu)的情況下,實(shí)現(xiàn)一些橫切關(guān)注點(diǎn)的邏輯。

  3. 全局性: Java Agent 可以在整個(gè) JVM 中生效,對(duì)加載的所有類都可以進(jìn)行轉(zhuǎn)換和增強(qiáng)。這使得你可以監(jiān)測(cè)、分析和增強(qiáng)全局的應(yīng)用行為。

Java Agent 的使用步驟

開發(fā) Java Agent 的涉及的要點(diǎn)如下圖所示:

要使用 Java Agent,通常需要遵循以下步驟:

  1. 創(chuàng)建代理類: 創(chuàng)建一個(gè)代理類,實(shí)現(xiàn) java.lang.instrument.Instrumentation 接口的 premain 方法。

  2. 注冊(cè)代理類: 在代理類的 premain 方法中注冊(cè)代理類。這將使代理類在 JVM 啟動(dòng)時(shí)加載并執(zhí)行。

  3. 定義轉(zhuǎn)換規(guī)則: 在代理類中,你可以使用 Instrumentation API 來定義轉(zhuǎn)換規(guī)則,即如何對(duì)類的字節(jié)碼進(jìn)行轉(zhuǎn)換。

示例:方法計(jì)時(shí)器 Java Agent

以下是一個(gè)簡單的 Java Agent 示例,實(shí)現(xiàn)對(duì)所有方法的計(jì)時(shí)統(tǒng)計(jì):

import?java.lang.instrument.Instrumentation;

public?class?MethodTimerAgent?{

????public?static?void?premain(String?agentArgs,?Instrumentation?instrumentation)?{
????????System.out.println("Agent?premain?called");
????????instrumentation.addTransformer(new?MethodTimerTransformer());
????}
}

增加創(chuàng)建一個(gè) ClassFileTransformer 接口的實(shí)現(xiàn)類 MethodTimerTransformer

public?class?MethodTimerTransformer?implements?ClassFileTransformer?{??
????private?final?static?String?prefix?=?"\nlong?startTime?=?System.currentTimeMillis();\n";??
????private?final?static?String?postfix?=?"\nlong?endTime?=?System.currentTimeMillis();\n";??

????//?被處理的方法列表??
????final?static?Map<String,?List<String>>?methodMap?=?new?HashMap<String,?List<String>>();??

????public?MethodTimerTransformer()?{??
????????add("com.example.TimeTest.sayHello");??
????}??

????private?void?add(String?methodString)?{??
????????String?className?=?methodString.substring(0,?methodString.lastIndexOf("."));??
????????String?methodName?=?methodString.substring(methodString.lastIndexOf(".")?+?1);??
????????List<String>?list?=?methodMap.get(className);??
????????if?(list?==?null)?{??
????????????list?=?new?ArrayList<String>();??
????????????methodMap.put(className,?list);??
????????}??
????????list.add(methodName);??
????}??

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

????????if?(className.startsWith("com/example/"))?{??
????????????className?=?className.replace("/",?".");??
????????????System.out.println("Transforming?class:?"?+?className);??

????????????//?在方法前后添加計(jì)時(shí)邏輯??
????????????CtClass?ctclass?=?null;??

????????????try?{??
????????????????ctclass?=?ClassPool.getDefault().get(className);//?使用全稱,用于取得字節(jié)碼類<使用javassist>??
????????????????System.out.println(ctclass.getName());??
????????????????for?(String?methodName?:?methodMap.get(className))?{??
????????????????????System.out.println(methodName);??
????????????????????String?outputStr?=?"\nSystem.out.println(\"this?method?"?+?methodName??
????????????????????+?"?cost:\"?+(endTime?-?startTime)?+\"ms.\");";??

????????????????????CtMethod?ctmethod?=?ctclass.getDeclaredMethod(methodName);//?得到這方法實(shí)例??
????????????????????String?newMethodName?=?methodName?+?"$old";//?新定義一個(gè)方法叫做比如sayHello$old??
????????????????????ctmethod.setName(newMethodName);//?將原來的方法名字修改??

????????????????????//?創(chuàng)建新的方法,復(fù)制原來的方法,名字為原來的名字??
????????????????????CtMethod?newMethod?=?CtNewMethod.copy(ctmethod,?methodName,?ctclass,?null);??

????????????????????//?構(gòu)建新的方法體??
????????????????????StringBuilder?bodyStr?=?new?StringBuilder();??
????????????????????bodyStr.append("{");??
????????????????????bodyStr.append(prefix);??
????????????????????bodyStr.append(newMethodName?+?"($$);\n");//?調(diào)用原有代碼,類似于method();($$)表示所有的參數(shù)??
????????????????????bodyStr.append(postfix);??
????????????????????bodyStr.append(outputStr);??
????????????????????bodyStr.append("}");??

????????????????????newMethod.setBody(bodyStr.toString());//?替換新方法??
????????????????????ctclass.addMethod(newMethod);//?增加新方法??
????????????????}??
????????????????return?ctclass.toBytecode();??
????????????}?catch?(Exception?e)?{??
????????????????System.out.println(e.getMessage());??
????????????????e.printStackTrace();??
????????????}??
????????}??

????????return?classfileBuffer;??
????}??
}

在上述示例中,MethodTimerAgent 是 Java Agent 的代理類,通過 premain 方法注冊(cè)了 MethodTimerTransformer 類。MethodTimerTransformer 是轉(zhuǎn)換器類,實(shí)現(xiàn)了 ClassFileTransformer 接口,允許你在 transform 方法中修改目標(biāo)類的字節(jié)碼。

這里的對(duì)字節(jié)碼的修改用到了javassist,javassist介紹可以參考字節(jié)碼增強(qiáng)技術(shù)-Javassist

想要其他項(xiàng)目使用,我們還需完成以下幾步:

創(chuàng)建文件resources/META-INF/MANIFEST.MF,內(nèi)容如下:

Manifest-Version:?1.0??
Can-Redefine-Classes:?true??
Can-Retransform-Classes:?true??
Premain-Class:?com.example.MethodTimerAgent??
Boot-Class-Path:?javassist-3.28.0-GA.jar

pom文件增加下面配置

<build>??
????<plugins>??
????????<plugin>??
????????????<groupId>org.apache.maven.plugins</groupId>??
????????????<artifactId>maven-jar-plugin</artifactId>??
????????????<version>2.4</version>??
????????????<configuration>??
????????????????<archive>??
????????????????<!--?就是把前面的配置的MANIFEST.MF打入jar中,以指定premain的位置,否則會(huì)報(bào):??
????????????????Failed?to?find?Premain-Class?manifest?attribute?in??????????????????-->??
????????????????<manifestFile>${maven.configuration.manifestFile}</manifestFile>??
????????????????</archive>??
????????????</configuration>??
????????</plugin>??
????</plugins>??
</build>

使用 Java Agent

我們新建一個(gè)項(xiàng)目,增加一個(gè)測(cè)試類:

public?class?TimeTest?{??
????public?static?void?main(String[]?args)?{??
????????sayHello();??
????}??

????public?static?void?sayHello()?{??
????????try?{??
????????????Thread.sleep(2000);??
????????????System.out.println("hello?world!!");??
????????}?catch?(InterruptedException?e)?{??
????????????e.printStackTrace();??
????????}??
????}??
}

要使用 Java Agent,你需要將代理類打包成 JAR 文件,并在啟動(dòng) JVM 時(shí)使用 -javaagent 參數(shù)指定該 JAR 文件的路徑。例如:

java?-javaagent:path/to/agent.jar?-jar?your-application.jar

我們這里直接在idea上測(cè)試:

增加VM Options參數(shù)

由于我們使用到了javassist,需要javassist的jar與java-agent的jar放在一起

最后我們運(yùn)行我們的測(cè)試類TimeTest,結(jié)果如下:

可以看到方法執(zhí)行耗時(shí)被打印出來了。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容