搜索
首页Javajava教程Java Agent怎么用

Java Agent怎么用

May 22, 2023 pm 08:52 PM
javaagent

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,一个用于实现测试目标程序。

Java Agent怎么用

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

    <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类完成输出方法执行时间的功能,

Java Agent怎么用

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

Java Agent怎么用

最终效果:

Java Agent怎么用

以上是Java Agent怎么用的详细内容。更多信息请关注PHP中文网其他相关文章!

声明
本文转载于:亿速云。如有侵权,请联系admin@php.cn删除
如何将Maven或Gradle用于高级Java项目管理,构建自动化和依赖性解决方案?如何将Maven或Gradle用于高级Java项目管理,构建自动化和依赖性解决方案?Mar 17, 2025 pm 05:46 PM

本文讨论了使用Maven和Gradle进行Java项目管理,构建自动化和依赖性解决方案,以比较其方法和优化策略。

如何使用适当的版本控制和依赖项管理创建和使用自定义Java库(JAR文件)?如何使用适当的版本控制和依赖项管理创建和使用自定义Java库(JAR文件)?Mar 17, 2025 pm 05:45 PM

本文使用Maven和Gradle之类的工具讨论了具有适当的版本控制和依赖关系管理的自定义Java库(JAR文件)的创建和使用。

如何使用咖啡因或Guava Cache等库在Java应用程序中实现多层缓存?如何使用咖啡因或Guava Cache等库在Java应用程序中实现多层缓存?Mar 17, 2025 pm 05:44 PM

本文讨论了使用咖啡因和Guava缓存在Java中实施多层缓存以提高应用程序性能。它涵盖设置,集成和绩效优势,以及配置和驱逐政策管理最佳PRA

如何将JPA(Java持久性API)用于具有高级功能(例如缓存和懒惰加载)的对象相关映射?如何将JPA(Java持久性API)用于具有高级功能(例如缓存和懒惰加载)的对象相关映射?Mar 17, 2025 pm 05:43 PM

本文讨论了使用JPA进行对象相关映射,并具有高级功能,例如缓存和懒惰加载。它涵盖了设置,实体映射和优化性能的最佳实践,同时突出潜在的陷阱。[159个字符]

Java的类负载机制如何起作用,包括不同的类载荷及其委托模型?Java的类负载机制如何起作用,包括不同的类载荷及其委托模型?Mar 17, 2025 pm 05:35 PM

Java的类上载涉及使用带有引导,扩展程序和应用程序类负载器的分层系统加载,链接和初始化类。父代授权模型确保首先加载核心类别,从而影响自定义类LOA

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
3 周前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳图形设置
3 周前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您听不到任何人,如何修复音频
3 周前By尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解锁Myrise中的所有内容
4 周前By尊渡假赌尊渡假赌尊渡假赌

热工具

适用于 Eclipse 的 SAP NetWeaver 服务器适配器

适用于 Eclipse 的 SAP NetWeaver 服务器适配器

将Eclipse与SAP NetWeaver应用服务器集成。

Atom编辑器mac版下载

Atom编辑器mac版下载

最流行的的开源编辑器

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

功能强大的PHP集成开发环境

VSCode Windows 64位 下载

VSCode Windows 64位 下载

微软推出的免费、功能强大的一款IDE编辑器

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境