溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

如何使用java-agent

發(fā)布時(shí)間:2021-10-12 15:13:35 來(lái)源:億速云 閱讀:114 作者:iii 欄目:編程語(yǔ)言

這篇文章主要講解了“如何使用java-agent”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來(lái)研究和學(xué)習(xí)“如何使用java-agent”吧!

靜態(tài)掛載

首先編寫java-agent的監(jiān)控程序,靜態(tài)掛載的入口函數(shù)為premain。premain函數(shù)有兩種,區(qū)別是傳入?yún)?shù)不同。通常選擇帶有Instrumentation參數(shù),可以使用該變量完成代碼的熱替換。

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

下面是一個(gè)簡(jiǎn)單的例子。在premain函數(shù)中,使用Instrumentation增加一個(gè)transformer。當(dāng)監(jiān)控的java應(yīng)用每次加載class的時(shí)候都會(huì)調(diào)用transformer。DefineTransformer是一個(gè)transformer,是ClassFileTransformer的實(shí)現(xiàn)。在它的transform函數(shù)的入?yún)⒅袝?huì)給出當(dāng)前加載的類名,類加載器等信息。樣例中我們只是打印了加載的類名。

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
import javassist.*;
public class PreMain {
 
    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){
            System.out.println("premain load Class:" + className);
            return classfileBuffer;
        }
    }
}

運(yùn)行java-agent需要將上述程序打包成一個(gè)jar文件,在jar文件的MANIFEST.MF中需要包含以下幾項(xiàng)

Can-Redefine-Classes: true
Can-Retransform-Classes: true
Premain-Class: com.huawei.PreMain

Premain-Class聲明了這個(gè)jar的premain函數(shù)所在的類,java-agent加載jar包時(shí)會(huì)在PreMain類中尋找premain。Can-Redefine-Classes與Can-Retransform-Classes聲明為true,表示允許這段程序修改java應(yīng)用的代碼。

如果你是使用Maven的項(xiàng)目,可以使用增加下面的插件來(lái)自動(dòng)添加MANIFEST.MF

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-assembly-plugin</artifactId>
    <version>2.6</version>
    <configuration>
        <appendAssemblyId>false</appendAssemblyId>
    <descriptorRefs>
        <descriptorRef>jar-with-dependencies</descriptorRef>
    </descriptorRefs>
    <archive>
        <manifest>
            <addClasspath>true</addClasspath>
        </manifest>
        <manifestEntries>
            <Premain-Class>com.huawei.PreMain</Premain-Class>
            <Can-Redefine-Classes>true</Can-Redefine-Classes>
            <Can-Retransform-Classes>true</Can-Retransform-Classes>
        </manifestEntries>
    </archive>
    </configuration>
    <executions>
    <execution>
        <id>assemble-all</id>
        <phase>package</phase>
        <goals>
        <goal>single</goal>
        </goals>
    </execution>
    </executions>
</plugin>

輸出jar文件之后,編寫一個(gè)hello world的java應(yīng)用編譯為hello.class,在啟動(dòng)應(yīng)用時(shí)使用如下命令

java -javaagent:/root/Java-Agent-Project-Path/target/JavaAgentTest-1.0-SNAPSHOT.jar hello

在執(zhí)行中就可以打印java虛擬機(jī)在運(yùn)行hello.class所加載的所有類。

java-agent的功能不僅限于輸出類的加載過(guò)程,通過(guò)下面這個(gè)樣例可以實(shí)現(xiàn)代碼的熱替換。首先編寫一個(gè)測(cè)試類。

public class App 
{
    public static void main( String[] args )
    {
        try{
            System.out.println( "main start!" );
 
            App test = new App();
            int x1 = 1;
            int x2 = 2;
            while(true){
                System.out.println(Integer.toString(test.add(x1, x2)));
                Thread.sleep(2000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
            System.out.println("main end");
        }
 
    }
 
    private int add(int x1, int x2){
        return x1+x2;
    }
}

然后我們修改PreMain類中transformer,并通過(guò)Instrumentation添加這個(gè)transformer。與DefineTransformer一樣。

static class MyClassTransformer implements ClassFileTransformer {
        @Override
        public byte[] transform(final ClassLoader loader,
                                final String className,
                                final Class<?> classBeingRedefined,
                                final ProtectionDomain protectionDomain,
                                final byte[] classfileBuffer) {
            // 如果當(dāng)前加載的類是我們編寫的測(cè)試類,進(jìn)入修改。
            if ("com/huawei/App".equals(className)) {
                try {
                    // 從ClassPool獲得CtClass對(duì)象
                    final ClassPool classPool = ClassPool.getDefault();
                    final CtClass clazz = classPool.get("com.huawei.App");
 
                    //打印App類中的所有成員函數(shù)
                    CtMethod[] methodList = clazz.getDeclaredMethods();
                    for(CtMethod method: methodList){
                        System.out.println("premain method: "+ method.getName());
                    }
 
                    // 獲取add函數(shù)并替換,$1表示函數(shù)的第一個(gè)入?yún)?
                    CtMethod convertToAbbr = clazz.getDeclaredMethod("add");
                    String methodBody = "{return $1 + $2 + 11;}";
                    convertToAbbr.setBody(methodBody);
 
                    // 在add函數(shù)體之前增加一段代碼,同理也可以在函數(shù)尾部添加
                    String methodBody = "System.out.println(Integer.toString($1));";
                    convertToAbbr.insertBefore(methodBody);
 
                    // 返回字節(jié)碼,并且detachCtClass對(duì)象
                    byte[] byteCode = clazz.toBytecode();
                    //detach的意思是將內(nèi)存中曾經(jīng)被javassist加載過(guò)的Date對(duì)象移除,如果下次有需要在內(nèi)存中找不到會(huì)重新走javassist加載
                    clazz.detach();
                    return byteCode;
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
            // 如果返回null則字節(jié)碼不會(huì)被修改
            return null;
        }
    }

之后的步驟與之前相同,運(yùn)行會(huì)發(fā)現(xiàn)add函數(shù)的邏輯已經(jīng)被替換了。

動(dòng)態(tài)掛載

動(dòng)態(tài)掛載是在應(yīng)用運(yùn)行過(guò)程中動(dòng)態(tài)的添加agent。技術(shù)原理是通過(guò)socket與目標(biāo)進(jìn)程通訊,發(fā)送load指令在目標(biāo)進(jìn)程掛載指定jar文件。agent執(zhí)行過(guò)程中的功能與靜態(tài)過(guò)載是完全相同的。在實(shí)施過(guò)程中,有幾點(diǎn)不同。首先入口函數(shù)名不同,動(dòng)態(tài)掛載的函數(shù)名是agentmain。與premain類似,有兩種格式。但通常采用帶有Instrumentation的那種。如下例所示

public class AgentMain {
 
    public static void agentmain(String agentArgs, Instrumentation instrumentation) throws UnmodifiableClassException {
        instrumentation.addTransformer(new MyClassTransformer(), true);
        instrumentation.retransformClasses(com.huawei.Test.class);
    }
 
    static class MyClassTransformer implements ClassFileTransformer {
        @Override
        public byte[] transform(final ClassLoader loader,
                                final String className,
                                final Class<?> classBeingRedefined,
                                final ProtectionDomain protectionDomain,
                                final byte[] classfileBuffer) {
            // 如果當(dāng)前加載的類是我們編寫的測(cè)試類,進(jìn)入修改。
            if ("com/huawei/App".equals(className)) {
                try {
                    // 從ClassPool獲得CtClass對(duì)象
                    final ClassPool classPool = ClassPool.getDefault();
                    final CtClass clazz = classPool.get("com.huawei.App");
 
                    //打印App類中的所有成員函數(shù)
                    CtMethod[] methodList = clazz.getDeclaredMethods();
                    for(CtMethod method: methodList){
                        System.out.println("premain method: "+ method.getName());
                    }
 
                    // 獲取add函數(shù)并替換,$1表示函數(shù)的第一個(gè)入?yún)?
                    CtMethod convertToAbbr = clazz.getDeclaredMethod("add");
                    String methodBody = "{return $1 + $2 + 11;}";
                    convertToAbbr.setBody(methodBody);
 
                    // 返回字節(jié)碼,并且detachCtClass對(duì)象
                    byte[] byteCode = clazz.toBytecode();
                    //detach的意思是將內(nèi)存中曾經(jīng)被javassist加載過(guò)的Date對(duì)象移除,如果下次有需要在內(nèi)存中找不到會(huì)重新走javassist加載
                    clazz.detach();
                    return byteCode;
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
            // 如果返回null則字節(jié)碼不會(huì)被修改
            return null;
        }
    }
}

功能與靜態(tài)加載相同。需要注意的是,Instrumentation增加了transformer之后,調(diào)用了retransformClasses函數(shù)。這是由于transformer只有在Java虛擬機(jī)加載class時(shí)才會(huì)調(diào)用。如果是通過(guò)動(dòng)態(tài)加載的方式,需要監(jiān)控的class文件可能已經(jīng)加載完成了。所以需要調(diào)用retransformClasses重新加載。

另外一點(diǎn)不同是MANIFEST.MF文件需要添加Agent-Class,如下所示

Can-Redefine-Classes: true
Can-Retransform-Classes: true
Premain-Class: com.huawei.PreMain
Agent-Class: com.huawei.AgentMain

最后一點(diǎn)不同是加載方式不同。動(dòng)態(tài)掛載需要編寫一個(gè)加載腳本。如下所示,在這段腳本中,首先遍歷所有的java進(jìn)程,通過(guò)啟動(dòng)類名辨識(shí)需要監(jiān)控的進(jìn)程。通過(guò)進(jìn)程id獲取VirtualMachine實(shí)例,并加載agentmain的jar文件。

import com.sun.tools.attach.*;
import java.io.IOException;
import java.util.List;
 
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) {
 
            System.out.println(vmd.displayName());
            String aim = "com.huawei.App";
            if (vmd.displayName().endsWith(aim)) {
                System.out.println(String.format("find %s, process id %s", vmd.displayName(), vmd.id()));
                VirtualMachine virtualMachine = VirtualMachine.attach(vmd.id());
                virtualMachine.loadAgent("/root/Java-Agent-Project-Path/target/JavaAgentTest-1.0-SNAPSHOT.jar");
                virtualMachine.detach();
            }
        }
    }
}

Scala程序監(jiān)控

Scala與Java兼容性很好,所以使用java-agent監(jiān)控scala應(yīng)用也是可行的。但是仍然需要注意一些問(wèn)題。第一點(diǎn)是程序替換只對(duì)class有作用,對(duì)object是無(wú)效的。第二個(gè)問(wèn)題是,動(dòng)態(tài)替換中是將程序編譯為字節(jié)碼之后再去替換的。java-agent使用的是java的編譯規(guī)則,所以替換程序要使用java的語(yǔ)言規(guī)則,否則會(huì)出現(xiàn)編譯錯(cuò)誤。例如示例中使用System.out.println輸出參數(shù)信息,如果使用scala的println會(huì)出現(xiàn)編譯錯(cuò)誤。

感謝各位的閱讀,以上就是“如何使用java-agent”的內(nèi)容了,經(jīng)過(guò)本文的學(xué)習(xí)后,相信大家對(duì)如何使用java-agent這一問(wèn)題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是億速云,小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI