Heim  >  Artikel  >  Java  >  So verwenden Sie den Java-Agenten

So verwenden Sie den Java-Agenten

PHPz
PHPznach vorne
2023-05-22 20:52:561817Durchsuche

Einführung in die Java-Agent-Technologie

Java-Agent wird wörtlich als Java-Agent übersetzt und wird oft auch als Java-Probe-Technologie bezeichnet.

Java Agent Diese Technologie wurde in JDK1.5 eingeführt und kann Java-Bytecode zur Laufzeit dynamisch ändern. Klassen in Java werden zu Bytecodes kompiliert, die von der JVM ausgeführt werden, bevor sie diese Bytecodes ausführen, und diese Bytecodes über einen Bytecode-Konverter ändern, um den Prozess abzuschließen.

Java Agent ist ein JAR-Paket, das nicht unabhängig ausgeführt werden kann. Es funktioniert über den an das Zielprogramm angehängten JVM-Prozess. Beim Start müssen Sie nur den Parameter -javaagent zu den Startparametern des Zielprogramms hinzufügen, um den Bytecode-Konverter ClassFileTransformer hinzuzufügen. Dies entspricht dem Hinzufügen eines Interceptors vor der Hauptmethode. ClassFileTransformer 字节码转换器,相当于在main方法前加了一个拦截器。

Java Agent 功能介绍

Java Agent 主要有以下功能:

  • Java Agent 能够在加载 Java 字节码之前拦截并对字节码进行修改;

  • Java Agent 能够在 Jvm 运行期间修改已经加载的字节码;

Java Agent 的应用场景:

  • IDE 的调试功能,例如 Eclipse、IntelliJ IDEA ;

  • 热部署功能,例如 JRebel、XRebel、spring-loaded;

  • 各种线上诊断工具,例如 Btrace、Greys,还有阿里的 Arthas;

  • 各种性能分析工具,例如 Visual VM、JConsole 等;

  • 全链路性能检测工具,例如 Skywalking、Pinpoint等;

Java Agent 实现原理

在了解Java Agent的实现原理之前,需要对Java类加载机制有一个较为清晰的认知。一种是在man方法执行之前,通过premain来执行,另一种是程序运行中修改,需通过JVM中的Attach实现,Attach的实现原理是基于JVMTI。

主要是在类加载之前,进行拦截,对字节码修改

下面我们分别介绍一下这些关键术语:

  • JVMTI 就是JVM Tool Interface,是 JVM 暴露出来给用户扩展使用的接口集合,JVMTI 是基于事件驱动的,JVM每执行一定的逻辑就会触发一些事件的回调接口,通过这些回调接口,用户可以自行扩展

JVMTI是实现 Debugger、Profiler、Monitor、Thread Analyser 等工具的统一基础,在主流 Java 虚拟机中都有实现

  • JVMTIAgent是一个动态库,利用JVMTI暴露出来的一些接口来干一些我们想做、但是正常情况下又做不到的事情,不过为了和普通的动态库进行区分,它一般会实现如下的一个或者多个函数:

    • Agent_OnLoad函数,如果agent是在启动时加载的,通过JVM参数设置

    • Agent_OnAttach函数,如果agent不是在启动时加载的,而是我们先attach到目标进程上,然后给对应的目标进程发送load命令来加载,则在加载过程中会调用Agent_OnAttach函数

    • Agent_OnUnload函数,在agent卸载时调用

  • javaagent 依赖于instrument的JVMTIAgent(Linux下对应的动态库是libinstrument.so),还有个别名叫JPLISAgent(Java Programming Language Instrumentation Services Agent),专门为Java语言编写的插桩服务提供支持的

  • instrument 实现了Agent_OnLoad和Agent_OnAttach两方法,也就是说在使用时,agent既可以在启动时加载,也可以在运行时动态加载。其中启动时加载还可以通过类似-javaagent:jar包路径的方式来间接加载instrument agent,运行时动态加载依赖的是JVM的attach机制,通过发送load命令来加载agent

  • JVM Attach 是指 JVM 提供的一种进程间通信的功能,能让一个进程传命令给另一个进程,并进行一些内部的操作,比如进行线程 dump,那么就需要执行 jstack 进行,然后把 pid 等参数传递给需要 dump 的线程来执行

Java Agent 案例

我们就以打印方法的执行时间为例,通过Java Agent 来实现。

首先我们需要构建一个精简的Maven

Einführung in die Funktion des Java-Agenten

So verwenden Sie den Java-Agenten

Java-Agent verfügt hauptsächlich über die folgenden Funktionen:

Java-Agent kann zuvor abgefangenen Java-Bytecode laden und den Bytecode geändert;

  • Der Java-Agent kann den geladenen Bytecode während der Ausführung von Jvm ändern;

  • Anwendungsszenarien des Java-Agenten:🎜🎜
    • 🎜Debugging-Funktionen von IDE, wie Eclipse, IntelliJ IDEA; 🎜
    • 🎜Hot-Deployment-Funktionen, wie JRebel, XRebel, Spring-Loaded; 🎜
    • 🎜Verschiedene Online-Diagnosetools wie Btrace, Greys und Arthas von Alibaba 🎜
    • 🎜Verschiedene Leistungsanalysetools wie Visual VM, JConsole usw. ;🎜
    • 🎜Full-Link-Leistungstesttools wie Skywalking, Pinpoint usw.;🎜
    🎜Java-Agent-Implementierungsprinzip🎜🎜Verstehen von Java-Agent Bevor Sie das Prinzip implementieren, sollten Sie Sie benötigen ein klares Verständnis des Java-Klassenlademechanismus. Eine besteht darin, das Programm vor der Ausführung der man-Methode auszuführen, was über Attach in der JVM implementiert werden muss. 🎜🎜 Hauptsächlich zum Abfangen und Ändern des Bytecodes vor dem Laden der Klasse 🎜🎜🎜 Lassen Sie uns jeweils diese Schlüsselbegriffe einführen: 🎜🎜
    • 🎜🎜JVMTI🎜 Es ist JVM Tool Schnittstelle, eine Sammlung von Schnittstellen, die von der JVM für Benutzererweiterungen bereitgestellt werden, ist jedes Mal, wenn die JVM eine bestimmte Logik ausführt, über diese Rückrufschnittstellen können Benutzer erweitert werden für sich🎜
    🎜JVMTI ist die einheitliche Basis für die Implementierung von Debugger, Profiler, Monitor, Thread Analyzer und anderen Tools und wird in gängigen virtuellen Java-Maschinen implementiert🎜
    • 🎜Agent_OnLoad Funktion, wenn der Agent beim Start geladen ist, durchsetzen JVM-Parameter 🎜
    • 🎜Agent_OnAttach Funktion, wenn der Agent beim Start nicht geladen wird, wir ihn jedoch zuerst an den Zielprozess anhängen und dann antworten. Der Zielprozess sendet den Ladebefehl an Beim Laden wird die Funktion Agent_OnAttach während des Ladevorgangs aufgerufen 🎜
    • 🎜 javaagent🎜 basiert auf dem JVMTIAgent des Instruments (die entsprechende dynamische Bibliothek unter Linux ist libinstrument.so), und es gibt auch eine Person namens JPLISAgent (Java Programming Language Instrumentation Services Agent), die speziell für The entwickelt wurde 🎜
    • 🎜🎜instrument🎜, unterstützt durch den in der Java-Sprache geschriebenen Instrumentierungsdienst, implementiert die beiden Methoden Agent_OnLoad und Agent_OnAttach, was bedeutet, dass der Agent bei Verwendung beim Start oder zur Laufzeit geladen werden kann. Das Laden beim Start kann auch indirekt über eine Methode geladen werden, die dem Paketpfad -javaagent:jar ähnelt. Das dynamische Laden zur Laufzeit basiert auf dem Anhängemechanismus der JVM. Der Agent wird durch Senden des Ladebefehls geladen /li>
    • 🎜🎜JVM Attach🎜 bezieht sich auf eine von JVM bereitgestellte prozessübergreifende Kommunikationsfunktion, die es einem Prozess ermöglicht, Befehle an einen anderen Prozess zu übergeben und einige interne Vorgänge wie Thread-Dump auszuführen. Anschließend müssen Sie Jstack ausführen. Übergeben Sie dann die PID und andere Parameter an den Thread, der zur Ausführung ausgegeben werden muss. 🎜
    Java Agent zu erreichen. 🎜🎜Zuerst müssen wir ein optimiertes Maven-Projekt erstellen, in dem wir zwei Maven-Unterprojekte erstellen, eines für die Implementierung des Plug-in-Agenten und eines für die Implementierung des Testzielprogramms. 🎜🎜🎜🎜🎜🎜Wir importieren die Pakete, von denen die beiden Projekte gemeinsame Abhängigkeiten haben, in die übergeordnete Anwendung🎜🎜
        <dependencies>
            <dependency>
                <groupId>org.javassist</groupId>
                <artifactId>javassist</artifactId>
                <version>3.28.0-GA</version>
            </dependency>
        </dependencies>
    🎜🎜Zuerst erstellen wir das Zielprogramm zum Testen🎜🎜
    // 启动类
    public class APPMain {
        public static void main(String[] args) {
            System.out.println("APP 启动!!!");
            AppInit.init();
        }
    }
    // 模拟的应用初始化的类
    public class AppInit {
        public static void init() {
            try {
                System.out.println("APP初始化中...");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    然后我们启动程序,测试是否能正常执行,程序正常执行之后,我们开始构建探针程序

    探针程序中我们需要编写,改变原有class的Transformer,通过自定义的Transformer类完成输出方法执行时间的功能,

    So verwenden Sie den Java-Agenten

    首先构检Agent程序的入口

    public class RunTimeAgent {
        public static void premain(String arg, Instrumentation instrumentation) {
            System.out.println("探针启动!!!");
            System.out.println("探针传入参数:" + arg);
            instrumentation.addTransformer(new RunTimeTransformer());
        }
    }

    这里每个类加载的时候都会走这个方法,我们可以通过className进行指定类的拦截,然后借助javassist这个工具,进行对Class的处理,这里的思想和反射类似,但是要比反射功能更加强大,可以动态修改字节码。

    javassist是一个开源的分析、编辑和创建Java字节码的类库。

    import javassist.ClassPool;
    import javassist.CtClass;
    import javassist.CtMethod;
    import java.lang.instrument.ClassFileTransformer;
    import java.lang.instrument.IllegalClassFormatException;
    import java.security.ProtectionDomain;
    public class RunTimeTransformer implements ClassFileTransformer {
        private static final String INJECTED_CLASS = "com.zhj.test.init.AppInit";
        @Override
        public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
            String realClassName = className.replace("/", ".");
            if (realClassName.equals(INJECTED_CLASS)) {
                System.out.println("拦截到的类名:" + realClassName);
                CtClass ctClass;
                try {
                    // 使用javassist,获取字节码类
                    ClassPool classPool = ClassPool.getDefault();
                    ctClass = classPool.get(realClassName);
                    // 得到该类所有的方法实例,也可选择方法,进行增强
                    CtMethod[] declaredMethods = ctClass.getDeclaredMethods();
                    for (CtMethod method : declaredMethods) {
                        System.out.println(method.getName() + "方法被拦截");
                        method.addLocalVariable("time", CtClass.longType);
                        method.insertBefore("System.out.println(\"---开始执行---\");");
                        method.insertBefore("time = System.currentTimeMillis();");
                        method.insertAfter("System.out.println(\"---结束执行---\");");
                        method.insertAfter("System.out.println(\"运行耗时: \" + (System.currentTimeMillis() - time));");
                    }
                    return ctClass.toBytecode();
                } catch (Throwable e) { //这里要用Throwable,不要用Exception
                    System.out.println(e.getMessage());
                    e.printStackTrace();
                }
            }
            return classfileBuffer;
        }
    }

    我们需要在Maven中配置,编译打包的插件,这样我们就可以很轻松的借助Maven生成Agent的jar包

    <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.5.1</version>
                    <!-- 指定maven编译的jdk版本。若不指定,maven3默认用jdk 1.5 maven2默认用jdk1.3 -->
                    <configuration>
                        <source>8</source>
                        <target>8</target>
                    </configuration>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-jar-plugin</artifactId>
                    <version>3.2.0</version>
                    <configuration>
                        <archive>
                            <!--自动添加META-INF/MANIFEST.MF -->
                            <manifest>
                                <addClasspath>true</addClasspath>
                            </manifest>
                            <manifestEntries>
                                <Menifest-Version>1.0</Menifest-Version>
                                <Premain-Class>com.zhj.agent.RunTimeAgent</Premain-Class>
                                <Can-Redefine-Classes>true</Can-Redefine-Classes>
                                <Can-Retransform-Classes>true</Can-Retransform-Classes>
                            </manifestEntries>
                        </archive>
                    </configuration>
                </plugin>
            </plugins>
        </build>

    否则我们需要在resources下创建META-INF/MANIFEST.MF文件,文件内容如下,我们可以看出这个与Maven中的配置是一致的,然后通过配置编译器,借助编译器打包成jar包,需指定该文件

    Manifest-Version: 1.0
    Premain-Class: com.zhj.agent.RunTimeAgent
    Can-Redefine-Classes: true
    Can-Retransform-Classes: true

    告示文件MANIFEST.MF参数说明:

    Manifest-Version

    文件版本

    Premain-Class

    包含 premain 方法的类(类的全路径名)main方法运行前代理

    Agent-Class

    包含 agentmain 方法的类(类的全路径名)main开始后可以修改类结构

    Boot-Class-Path

    设置引导类加载器搜索的路径列表。查找类的特定于平台的机制失败后,引导类加载器会搜索这些路径。按列出的顺序搜索路径。列表中的路径由一个或多个空格分开。(可选)

    Can-Redefine-Classes true

    表示能重定义此代理所需的类,默认值为 false(可选)

    Can-Retransform-Classes true

    表示能重转换此代理所需的类,默认值为 false (可选)

    Can-Set-Native-Method-Prefix true

    表示能设置此代理所需的本机方法前缀,默认值为 false(可选)

    最后通过Maven生成Agent的jar包,然后修改测试目标程序的启动器,添加JVM参数即可

    参数示例:-javaagent:F:\code\myCode\agent-test\runtime-agent\target\runtime-agent-1.0-SNAPSHOT.jar=hello

    So verwenden Sie den Java-Agenten

    最终效果:

    So verwenden Sie den Java-Agenten

    Das obige ist der detaillierte Inhalt vonSo verwenden Sie den Java-Agenten. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

    Stellungnahme:
    Dieser Artikel ist reproduziert unter:yisu.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen