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 字节码之前拦截并对字节码进行修改;
Java Agent 能够在 Jvm 运行期间修改已经加载的字节码;
Java Agent 的应用场景:
IDE 的调试功能,例如 Eclipse、IntelliJ IDEA ;
热部署功能,例如 JRebel、XRebel、spring-loaded;
各种线上诊断工具,例如 Btrace、Greys,还有阿里的 Arthas;
各种性能分析工具,例如 Visual VM、JConsole 等;
全链路性能检测工具,例如 Skywalking、Pinpoint等;
在了解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
来实现。
首先我们需要构建一个精简的Maven
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;
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 : 🎜🎜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🎜Agent_OnLoad
, si l'agent est en Chargé au démarrage, paramétré via Paramètres JVM 🎜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🎜Agent_OnUnload
, et la fonction Agent_OnAttach sera appelée lorsque l'agent est déchargé >JPLISAgent
(Java Programming Language Instrumentation Services Agent), spécialement conçu pour 🎜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🎜<.> 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>
// 启动类 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类完成输出方法执行时间的功能,
首先构检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
最终效果:
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!