Maison >Java >javaDidacticiel >Comment utiliser l'agent Java

Comment utiliser l'agent Java

PHPz
PHPzavant
2023-05-22 20:52:561874parcourir

Introduction à la technologie Java Agent

Java Agent est littéralement traduit par agent Java et est également souvent appelé technologie de sonde Java.

Java Agent Cette technologie a été introduite dans JDK1.5 et peut modifier dynamiquement le bytecode Java au moment de l'exécution. Les classes en Java sont compilées pour former des bytecodes qui sont exécutés par la JVM. La JVM obtient les informations de ces bytecodes avant d'exécuter ces bytecodes et modifie ces bytecodes via un convertisseur de bytecode pour terminer le processus.

Java Agent est un package jar qui ne peut pas s'exécuter indépendamment. Il fonctionne via le processus JVM attaché au programme cible. Au démarrage, il vous suffit d'ajouter le paramètre -javaagent aux paramètres de démarrage du programme cible pour ajouter le convertisseur de bytecode ClassFileTransformer, ce qui équivaut à ajouter un intercepteur avant la méthode principale. 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

Introduction à la fonction Java Agent

Comment utiliser lagent Java

Java Agent a principalement les fonctions suivantes :

Java Agent peut charger le bytecode Java Précédemment intercepté et modifié le bytecode;

  • Java Agent peut modifier le bytecode chargé pendant l'exécution de Jvm;

  • Scénarios d'application de Java Agent :🎜🎜
    • 🎜Fonctions de débogage de l'EDI, telles qu'Eclipse, IntelliJ IDEA ; 🎜
    • 🎜Fonctions de déploiement à chaud, telles que JRebel, XRebel, à ressort ; 🎜
    • 🎜Divers outils de diagnostic en ligne, tels que Btrace, Greys et Arthas d'Alibaba ; 🎜
    • 🎜Divers outils d'analyse des performances, tels que Visual VM, JConsole, etc. ;🎜
    • 🎜Outils de test de performances à lien complet, tels que Skywalking, Pinpoint, etc. ;🎜
    🎜Principe de mise en œuvre de l'agent Java🎜🎜Comprendre l'Java Agent Avant de mettre en œuvre le principe, vous Vous devez avoir une compréhension claire du mécanisme de chargement des classes Java. L'une consiste à exécuter via premain avant l'exécution de la méthode man. L'autre consiste à modifier le programme pendant son exécution, ce qui doit être implémenté via Attach dans la JVM. Le principe d'implémentation d'Attach est basé sur JVMTI. 🎜🎜 Principalement pour intercepter et modifier le bytecode avant le chargement de la classe 🎜🎜🎜 Introduisons respectivement ces termes clés : 🎜🎜
    • 🎜🎜JVMTI🎜 Il s'agit de JVM Tool Interface, qui est une collection d'interfaces exposées par la JVM pour les extensions utilisateur, JVMTI est pilotée par les événements. Chaque fois que la JVM exécute une certaine logique, elle déclenche certaines interfaces de rappel d'événements. Grâce à ces interfaces de rappel, les utilisateurs peuvent développer. par eux-mêmes🎜
    🎜JVMTI est la base unifiée pour l'implémentation du débogueur, du profileur, du moniteur, de l'analyseur de threads et d'autres outils, et est implémenté dans les machines virtuelles Java grand public🎜
    • 🎜🎜JVMTIAgent🎜 est une bibliothèque dynamique qui utilise certaines interfaces exposées par JVMTI pour faire des choses que nous voulons faire mais que nous ne pouvons pas faire dans des circonstances normales, mais afin de la distinguer des bibliothèques dynamiques ordinaires, 🎜Il implémente généralement une ou plusieurs fonctions comme suit : 🎜🎜
      • 🎜 Fonction Agent_OnLoad, si l'agent est en Chargé au démarrage, paramétré via Paramètres JVM 🎜
      • 🎜 Fonction Agent_OnAttach, si l'agent n'est pas chargé au démarrage, mais que nous nous attachons d'abord au processus cible, puis répondons Le processus cible envoie la commande de chargement à charge, la fonction Agent_OnAttach sera appelée pendant le processus de chargement🎜
      • 🎜Fonction Agent_OnUnload, et la fonction Agent_OnAttach sera appelée lorsque l'agent est déchargé >
      • 🎜🎜. javaagent🎜 s'appuie sur JVMTIAgent d'instrument (la bibliothèque dynamique correspondante sous Linux est libinstrument.so), et il existe également un individu nommé JPLISAgent (Java Programming Language Instrumentation Services Agent), spécialement conçu pour 🎜
      • 🎜🎜instrument🎜 supporté par le service d'instrumentation écrit en langage Java implémente les deux méthodes Agent_OnLoad et Agent_OnAttach, ce qui signifie que lorsqu'il est utilisé, l'agent peut être chargé au démarrage ou au chargement dynamique. Le chargement au démarrage peut également charger indirectement l'agent d'instrument via une méthode similaire au chemin du package -javaagent:jar. Le chargement dynamique au moment de l'exécution repose sur le mécanisme d'attache de la JVM. L'agent est chargé en envoyant la commande de chargement🎜<.>
      • 🎜🎜JVM Attach🎜 fait référence à une fonction de communication inter-processus fournie par JVM, qui permet à un processus de transmettre des commandes à un autre processus et d'effectuer certaines opérations internes, telles que le vidage de thread, vous devez alors exécuter jstack, puis transmettez le pid et d'autres paramètres au thread qui doit être dumpé pour exécution🎜
      🎜Cas de l'agent Java🎜🎜Nous prendrons le temps d'exécution de la méthode d'impression comme exemple, via Agent Java à réaliser. 🎜🎜Nous devons d'abord créer un projet <code>Maven rationalisé, dans lequel nous construisons deux sous-projets Maven, un pour implémenter l'agent de plug-in et un pour implémenter le programme cible de test. 🎜🎜🎜🎜🎜🎜Nous importons les packages sur lesquels les deux projets ont des dépendances communes dans l'application parent🎜🎜
          <dependencies>
              <dependency>
                  <groupId>org.javassist</groupId>
                  <artifactId>javassist</artifactId>
                  <version>3.28.0-GA</version>
              </dependency>
          </dependencies>
      🎜🎜Nous construisons d'abord le programme cible pour les tests🎜🎜
      // 启动类
      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类完成输出方法执行时间的功能,

      Comment utiliser lagent Java

      首先构检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

      Comment utiliser lagent Java

      最终效果:

      Comment utiliser lagent Java

    Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

    Déclaration:
    Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer