千家信息网

Java Agent怎么用

发表于:2025-01-19 作者:千家信息网编辑
千家信息网最后更新 2025年01月19日,这篇文章主要介绍"Java Agent怎么用",在日常操作中,相信很多人在Java Agent怎么用问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答"Java Agent怎
千家信息网最后更新 2025年01月19日Java Agent怎么用

这篇文章主要介绍"Java Agent怎么用",在日常操作中,相信很多人在Java Agent怎么用问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答"Java Agent怎么用"的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

Java Agent 技术简介

Java Agent 直译为 Java 代理,也常常被称为 Java 探针技术。

Java Agent 是在 JDK1.5 引入的,是一种可以动态修改 Java 字节码的技术。Java 中的类编译后形成字节码被 JVM 执行,在 JVM 在执行这些字节码之前获取这些字节码的信息,并且通过字节码转换器对这些字节码进行修改,以此来完成一些额外的功能。

Java Agent 是一个不能独立运行 jar 包,它通过依附于目标程序的 JVM 进程,进行工作。启动时只需要在目标程序的启动参数中添加-javaagent 参数添加 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项目,在其中构建两个Maven的子项目,一个用于实现外挂的Agent,一个用于实现测试目标程序。

我们在父应用中导入两个项目公共依赖的包

                        org.javassist            javassist            3.28.0-GA            

首先我们去构建测试的目标程序

// 启动类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包

                                    org.apache.maven.plugins                maven-compiler-plugin                3.5.1                                                    8                    8                                                        org.apache.maven.plugins                maven-jar-plugin                3.2.0                                                                                                                true                                                                            1.0                            com.zhj.agent.RunTimeAgent                            true                            true                                                                                    

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

Manifest-Version: 1.0Premain-Class: com.zhj.agent.RunTimeAgentCan-Redefine-Classes: trueCan-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

最终效果:

到此,关于"Java Agent怎么用"的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注网站,小编会继续努力为大家带来更多实用的文章!

0