Heim >Java >javaLernprogramm >So verwenden Sie den Java-Agenten
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 字节码之前拦截并对字节码进行修改;
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
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;
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: 🎜🎜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🎜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 🎜JPLISAgent
(Java Programming Language Instrumentation Services Agent), die speziell für The entwickelt wurde 🎜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类完成输出方法执行时间的功能,
首先构检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
最终效果:
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!