Java代理之Java Agent分析

news/2024/11/8 18:33:58/文章来源:https://www.cnblogs.com/jingzh/p/18535625

目录
  • 1 Java Agent
    • 1.1 简介
      • 1.1.1 定义
      • 1.1.2 与代理区别
      • 1.1.3 主要功能和用途
    • 1.2 原理和模式
    • 1.3 使用实现
      • 1.3.1 Premain 模式
        • 1.3.1.1 创建Agent类
        • 1.3.1.2 配置Maven
        • 1.3.1.3 启动程序时指定
      • 1.3.2 Agentmain模式
        • 1.3.2.1 通过 Attach API 动态注入
        • 1.3.2.2 启动Agent
    • 1.4 Instrumentation接口
      • 1.4.1 核心功能
      • 1.4.2 典型用法
      • 1.4.3 操作示例

1 Java Agent

1.1 简介

1.1.1 定义

Java Agent 是一种用于在 Java 应用启动或运行过程中对其进行监控、修改或增强的机制。它利用 Java Instrumentation API,可以在应用启动时或运行中动态加载代码,对目标应用的字节码进行操作。这种机制非常适合应用监控、性能分析、调试、代码注入和安全性增强等任务。

简单来说,Java Agent 就是运行在 Java 虚拟机(JVM)上的一种工具,能在程序运行时对其进行监控、修改甚至重定义。它的作用和 AOP(面向切面编程)有点类似,但更加底层,直接作用在 JVM 层面。可以理解为它是全局的 AOP,能在类加载、方法执行等时刻动态插手程序行为。

1.1.2 与代理区别

Java Agent 可以说是一种“代理”工具,但它的代理作用和一般的代理(例如 Java 中的 Proxy 类)有些不同。Java Agent 主要是通过修改类字节码的方式,实现在不直接修改原始代码的情况下对程序的运行行为进行增强或拦截。

Java Agent 和普通代理的区别:

  • 字节码层面的代理:Java Agent 是在类加载时,通过字节码操作来修改类的定义,因此属于低层次的代理。这不同于使用 Java 动态代理或 CGLIB 代理,它不需要在代码中显式调用代理方法。
  • 无侵入性:Java Agent 能够在应用启动或运行时注入代理逻辑,不需要修改原始代码。比如 APM 工具的 Java Agent 就能自动为应用添加性能监控,无需在每个方法中手动添加监控代码。
  • 全局作用:Java Agent 可以对 JVM 中的所有类进行代理操作(包括 JDK 自带类),并不是针对某个对象或接口的代理。代理逻辑可以应用于整个 JVM 中加载的所有类,适用范围更广。

与普通代理的对比

特性 Java Agent Java 动态代理 / CGLIB 代理
代理方式 字节码操作 接口或子类方法拦截
实现时机 JVM 启动时 / 运行时注入 编码时指定代理逻辑
侵入性 无侵入,自动加载 需要在代码中显式调用代理类
作用范围 全局所有类 某个对象或接口
典型用途 性能监控、日志注入、调试等 业务逻辑中的代理模式

1.1.3 主要功能和用途

主要作用:

  • 性能监控:可以捕获应用程序的性能数据,比如方法调用次数、执行时间、内存消耗等,生成性能报告。例如,常见的 APM(应用性能监控)工具如 New Relic、Dynatrace 等都使用了 Java Agent 技术。
  • 字节码增强:在类加载时修改类的字节码,比如添加日志、修改方法逻辑、实现代码注入等。Java Agent 可以在应用运行时拦截并修改方法,使其在不改变原始代码的情况下增加额外功能。
  • 动态调试:在不重启应用的情况下动态附加 Java Agent,可以实时监控或调试生产环境中的问题。
  • 应用安全性:可以为应用增加安全性检查,例如在方法调用前加入权限验证,或在检测到异常行为时触发报警。
  • 测试增强:可以利用 Java Agent 对应用内部行为进行模拟或监控,增强自动化测试或集成测试的功能。

1.2 原理和模式

Java Agent 使用 java.lang.instrument.Instrumentation 接口来对类的字节码进行修改。其基本流程如下:

  • 创建代理类:编写一个含有 premainagentmain 方法的代理类。premain 用于在应用启动时加载,agentmain 用于在应用运行时动态附加。
  • 实现字节码操作:在代理类中,通过 Instrumentation 对象,可以拦截和修改字节码,比如用 Java ASM 或 Javassist 等字节码工具来修改类文件。
  • 打包和运行:将代理类打包为 jar 并设置清单文件中的 Premain-Class 或 Agent-Class 属性,使 Java 在启动时加载该代理。

Java Agent 主要有两种模式:Premain模式和Agentmain模式:

  • Premain模式:在程序启动前就能注入
    这种模式通常是我们在程序启动时就注入 Agent,常见于应用启动时的初始化操作。
    例如:在程序启动时,配置一些监控、日志、性能分析工具。通过这种方式,Agent 可以在应用的生命周期中,从一开始就进行干预。
    使用场景:初始化操作、性能监控、日志收集等。
  • Agentmain模式:动态注入Agent
    这种模式是指在程序启动后,动态地将 Agent 注入到正在运行的 JVM 中。在主程序已经启动并且运行的过程中,也可以通过一些工具(比如 attach API)把 Agent 加入到 JVM 中。
    这种方式主要用于热更新和动态调试。
    使用场景:热部署、动态调整配置、动态监控等。

1.3 使用实现

1.3.1 Premain 模式

1.3.1.1 创建Agent类

首先,我们需要创建一个 Java 类,通常这个类会有一个静态方法 premain,它会在主程序启动前被执行。

import java.lang.instrument.Instrumentation;public class MyAgent {// premain方法会在main方法之前执行public static void premain(String agentArgs, Instrumentation inst) {System.out.println("Java Agent initialized!");// 注册一个类的转换器inst.addTransformer(new MyClassFileTransformer());}static class MyClassFileTransformer implements ClassFileTransformer {@Overridepublic byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,ProtectionDomain protectionDomain, byte[] classfileBuffer) {// 这里可以对字节码进行修改System.out.println("Transforming class: " + className);return classfileBuffer; // 返回修改后的字节码}}
}

1.3.1.2 配置Maven

我们需要通过 Maven 配置项目的构建方式,将这个 Agent 类打包成一个 JAR 文件。关键在于 MANIFEST.MF 文件中的配置,需要指定 Agent 类的入口点。

<build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-jar-plugin</artifactId><version>3.1.0</version><configuration><archive><manifestEntries><Premain-Class>com.example.MyAgent</Premain-Class></manifestEntries></archive></configuration></plugin></plugins>
</build>

1.3.1.3 启动程序时指定

打包好之后,我们只需在启动程序时通过 -javaagent 参数来指定这个 Agent JAR 文件。例如:

java -javaagent:/path/to/myagent.jar -jar myapp.jar

参数说明:

  • -javaagent:参数后面跟的是一个 Java 代理的 JAR 文件路径。这个代理可以在应用程序启动之前或运行期间对字节码进行修改或增强,常用于性能监控、日志记录等功能。
  • /path/to/myagent.jarJava 代理的 JAR 文件的完整路径
  • -jar myapp.jar:指定了要运行的主应用程序的 JAR 文件路径

1.3.2 Agentmain模式

假如要在程序运行时动态注入一个 Java Agent,可以使用 Agentmain 模式。这种方式可以在程序启动之后,通过附加到一个已经在运行的 JVM 来注入代码。

1.3.2.1 通过 Attach API 动态注入

这种方式依赖于 Attach API,它允许在程序运行时,将一个新的 Agent 附加到正在运行的 JVM 上。

import com.sun.tools.attach.*;public class AgentAttacher {public static void main(String[] args) throws Exception {String pid = args[0];  // 获取目标进程的PIDString agentJarPath = args[1];  // 要注入的Agent路径// 获取目标JVM的虚拟机进程VirtualMachine vm = VirtualMachine.attach(pid);// 向目标JVM进程注入Agentvm.loadAgent(agentJarPath);vm.detach();  // 注入后断开与目标JVM的连接}
}

这段代码通过 VirtualMachine.attach(pid) 连接到目标 JVM 进程,然后通过 loadAgent() 方法将 Java Agent 动态注入。这里的 pid 就是目标 JVM 进程的 ID,你可以通过工具(如 jps)来获取。

1.3.2.2 启动Agent

Premain模式 不同的是,Agent 在这种模式下并不需要在程序启动时就指定,而是可以在程序运行中后期动态地附加进去。

1.4 Instrumentation接口

InstrumentationJava Agent 的核心接口,它提供了修改和操作 JVM 中加载的类的能力。通过 Instrumentation,可以修改类字节码、重定义已有类,甚至能在类加载时插手,动态地修改类行为。

1.4.1 核心功能

Instrumentation 的核心功能:
Instrumentation 接口的功能非常丰富,以下是一些关键功能及其用途:

  • 修改类定义:
    可以在类加载前,通过 ClassFileTransformer 对类字节码进行修改。
    使用 redefineClasses 方法在类已经加载后重新定义该类,这样可以在运行时修改类的行为。
  • 添加和移除 ClassFileTransformer
    ClassFileTransformer 是一个用于修改类字节码的接口。通过 Instrumentation,可以将一个 ClassFileTransformer 添加到 JVM 中,监控或更改所有类的字节码。
    可以使用 addTransformer 方法将 ClassFileTransformer 添加到 Instrumentation 实例中,之后每次加载类时都会触发 transform 方法进行字节码修改。
  • 获取对象大小:
    使用 getObjectSize(Object object) 可以获取某个对象的大小,主要用于内存分析工具中。它可以精确地获取 Java 对象在内存中的占用空间。
  • 动态代理:
    Instrumentation 可以在运行时创建动态代理类,这样可以为现有的对象添加新的方法或行为。代理类可以拦截方法调用,实现方法增强。
  • 检索所有加载的类:
    getAllLoadedClasses() 方法可以返回 JVM 中所有已经加载的类,方便进行全局监控或分析。
  • 检测类是否已加载:
    使用 isModifiableClass(Class<?> theClass) 方法可以检查某个类是否可以修改,以避免对不支持的类进行重新定义而导致错误。
  • 添加类卸载事件处理器:
    Instrumentation 提供了类卸载的通知支持,可以用来监控类的卸载事件。可以用于记录对象的生命周期,监控资源的使用情况等。

1.4.2 典型用法

以下是一些 Instrumentation 的常见用法场景:

  • 性能监控工具(APM):
    可以通过 ClassFileTransformer 修改类字节码,添加方法进入和退出的时间记录,从而计算方法的执行时间,并汇总性能数据。
  • 内存监控:
    可以通过 getObjectSize 方法估算内存中对象的实际大小,结合类加载监控来分析内存泄露等问题。
  • 调试和测试工具:
    可以对类的行为进行修改,注入调试信息或测试代码。
    例如,在测试时可以通过 redefineClasses 修改类定义,不用重启应用来验证新代码的逻辑。
  • 安全增强:
    在类加载时对字节码进行检查或修改,防止某些不安全的操作或方法被调用,提高程序的安全性。

1.4.3 操作示例

以下是一个例子,展示了如何使用 Instrumentation 来修改类的字节码:

import java.lang.instrument.*;public class MyClassTransformer implements ClassFileTransformer {@Overridepublic byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,ProtectionDomain protectionDomain, byte[] classfileBuffer) {if (className.equals("com/example/MyClass")) {// 这里可以使用 Javassist 或 ASM 等库来修改字节码System.out.println("Transforming MyClass...");// 返回修改后的字节码return modifiedClassBytecode;}return null;}
}

Java Agent 最常见的应用之一是性能监控。举个例子,我们可以通过 Agent 动态地修改类的字节码,来插入一些监控代码,记录方法执行时间、内存使用等信息。通过这种方式,我们无需修改现有代码,只需通过 Agent 即可实现监控。

比如,要监控某个方法的执行时间,可以在方法的入口和出口插入日志代码,记录执行时间:

public class MyClass {public void myMethod() {long start = System.currentTimeMillis();// 方法逻辑long end = System.currentTimeMillis();System.out.println("Method executed in " + (end - start) + " ms");}
}

通过 Agent 插入这个监控代码,可以动态获取到该方法的执行时间,无需修改源代码。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/828953.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【架构】异地多活架构设计

一、关于基础架构二、关于异地多活三、写时延是关键3.1 核心在于数据层的写操作3.2 写时延在跨城时发生质变3.3 同步复制缩短距离降目标3.4 异步复制就近分片做有损四、写量大拆分片五、做隔离拆分片六、其他影响因素6.1 读时延可就近6.2 读量大扩副本6.3 连接多加代理七、数据…

1.11--04:网线主管

http://noi.openjudge.cn/ch0111/04/网线主管 思路题目要求保留小数点后两位,说明啥,如果我们要循环找答案,每次只能+0.01,不然容易错过答案,看这个数据范围,一看就炸了考虑到二分怎么分?众所周知,二分的前提条件是内容必须有一定的规律如果当前导线和除以中间长度(我们…

【MySQL】数据库备份详解

一、引言1.1 数据库备份的重要性二、MySQL数据库备份的基础知识2.1 备份类型2.2 备份工具与方法三、MySQL数据库备份的实施步骤3.1 环境准备3.2 选择合适的备份工具与方法3.3 执行备份3.4 验证备份3.5 存储与管理备份四、MySQL数据库恢复流程4.1 mysqldump备份的恢复4.2 物理备…

关于虚拟仿真云实验教学_解决方案及优势介绍!

在科技飞速演进的潮流下,虚拟仿真技术正不断蓬勃发展,成为教育领域的一颗耀眼之星。作为创新的教育手段,虚拟仿真云教学正逐渐受到越来越多教育机构的高度重视与广泛应用,本文将为您详细探讨虚拟仿真云实验教学的解决方案及其所带来的多重优势。在科技飞速演进的潮流下,虚…

08C++选择结构(2)——教学

一、逻辑变量 教学视频 存储类似灯亮或灯灭、是男还是女等结果只有两种可能的数据时,可以使用逻辑型变量。 逻辑型变量用关键字bool定义,所以又称为布尔变量,其值只有两个false(假)和true(真),false和true是逻辑常量,又称布尔常量。 流程图如下:英汉小词典: bool:布…

我的二次元相册又回来了

从最早的图库,到图床,在到现在的相册.我对图片的执着已经可以说是跨越了好几个世纪了. 图库的图片是存在七牛云的,后来流量被刷就关闭了. 图床,现在转为为博客提供图片上传服务了. 相册,就是手机中相册的概念,它既可以存图片也可以放视频. 逐渐是越来越完善了 这两天就把平常保…

劫持微信聊天记录并分析还原 —— 合并解密后的数据库(三)

程序以 Python 语言开发,可读取、解密、还原微信数据库并帮助用户查看聊天记录,还可以将其聊天记录导出为csv、html等格式用于AI训练,自动回复或备份等等作用。本工具设计的初衷是用来获取微信账号的相关信息并解析PC版微信的数据库。程序以 Python 语言开发,可读取、解密、…

ABC377

C link存一下那些点不能占,用总数减去即可,注意存的时候可以用一个\(map\),存过的就不要再存了。

新配置!米尔新唐MA35D1核心板512M DDR配置发布!

米尔在2024年8月推出了基于新唐MA35D1芯片设计的嵌入式处理器模块MYC-LMA35核心板及开发板。MA35D1是集成2个Cortex-A35与1个Cortex-M4的异构微处理器芯片。核心板采用创新LGA 252PIN设计,原生17路UART和4路CAN FD等丰富的通讯接口,可广泛应用于新能源充电桩、工程机械控制器…

将本地nuget包推送到Nexus

1.安装nuget.exe ,下载地址https://www.nuget.org/downloads,下载后直接将nuget.exe拷贝到C:\Windows\System32目录下2.cmd执行 nuget setapikey e500146f-8594-32a3-9041-6ad7d2bf8d9b -source http://192.168.10.22:8081/repository/nuget-hosted/ 为仓库设置apikey3.执行…

Python之字符类型

一、索引 索引在公司中一般也叫下标,或角标 定义:可我们可以直接使用索引来访问序列中的元素,同时索引可分为正向索引和负向索引两种,而切片也会用到索引,如下图:Python中有序列:字符,列表,元组 无序:集合 正向索引:从0开始 负向索引:-1开始 二、切片 定义:切片是…