Agent使用
目录
Agent使用
关键类:Instrumentation
Instrumentation 的主要功能
| 功能 | 描述 | 应用场景 |
|---|---|---|
| redefineClasses | 替换类的字节码 | 热修复、功能更新 |
| retransformClasses | 重新应用转换器 | 动态启用监控 |
| addTransformer | 注册字节码转换器 | APM、日志增强 |
| getAllLoadedClasses | 获取所有已加载类 | 类分析、诊断 |
| getObjectSize | 计算对象大小 | 内存分析 |
| setNativeMethodPrefix | 拦截 native 方法 | native 方法监控 |
总结:Instrumentation 确实是 Java 平台上最强大的工具之一,它提供了在运行时修改和监控 Java 程序的底层能力,是很多高级工具(如 APM、热修复、诊断工具)的技术基础。
Instrumentation 确实是 Agent 技术的核心接口。
// 1. 🔥 核心接口 - 功能入口
java.lang.instrument.Instrumentation
// 2. 🔥 核心类 - 字节码转换
java.lang.instrument.ClassFileTransformer
// 3. 🔥 核心类 - 类定义
java.lang.instrument.ClassDefinition
// 4. 🔥 核心工具 - 动态附加
com.sun.tools.attach.VirtualMachine
Java Agent 技术的核心意义:
| 维度 | 传统方式 | Agent 增强方式 | 意义 |
|---|---|---|---|
| 问题诊断 | 靠猜+日志 | 实时观测 | 效率提升10倍+ |
| 故障恢复 | 重启应用 | 热修复 | 业务零中断 |
| 系统监控 | 外部指标 | 内部状态 | 从黑盒到白盒 |
| 成本控制 | 高人力成本 | 自动化诊断 | 成本大幅降低 |
| 用户体验 | 服务中断 | 持续可用 | 体验大幅提升 |
| 技术债务 | 难以处理 | 动态增强 | 延长系统寿命 |
本质上,Agent 技术让 Java 应用从"静态的、需要重启的"变成了"动态的、可实时操作的",这是运维和开发模式的革命性进步。
Arthas已经成为agent的代名词,直接使用 Arthas 就够了直接用arthas就行,不用我们自己写了,arthas够用。直接用 Arthas,除非你有非常特殊的定制化需求!
使用规则
-
方法签名规则
// premain 方法签名 // 标准签名(推荐) public static void premain(String agentArgs, Instrumentation inst) // 简化签名(可选) public static void premain(String agentArgs) // agentmain 方法签名 // 标准签名(推荐) public static void agentmain(String agentArgs, Instrumentation inst) // 简化签名(可选) public static void agentmain(String agentArgs) -
MANIFEST.MF 配置规则(关键!)
# META-INF/MANIFEST.MF # premain 必须配置 Premain-Class: com.example.MyAgent # agentmain 必须配置 Agent-Class: com.example.MyAgent # 热更相关能力 Can-Redefine-Classes: true Can-Retransform-Classes: true # 动态附加需要 Can-Attach: true# 完整的 MANIFEST.MF 示例 Manifest-Version: 1.0 Premain-Class: com.example.HotFixAgent Agent-Class: com.example.HotFixAgent Can-Redefine-Classes: true Can-Retransform-Classes: true Can-Attach: true Boot-Class-Path: your-agent.jar Created-By: 1.8.0_292 (Oracle Corporation)
规则总结:
- ✅ 方法名必须准确:
premain/agentmain - ✅ 方法签名必须正确:
public static void - ✅ MANIFEST.MF 必须配置:
Premain-Class/Agent-Class - ✅ 权限必须声明:
Can-Redefine-Classes等 - ✅ 打包必须包含清单:JAR 包中要有正确的 MANIFEST.MF
简单说:JVM 通过 方法名 + MANIFEST.MF 配置 来识别和调用 Agent,两者缺一不可!
标准目录结构
my-agent-project/
├── src/
│ └── com/example/
│ └── HotFixAgent.java
├── META-INF/
│ └── MANIFEST.MF
└── pom.xml (如果使用 Maven)
Agent实现热更
agentmain方法和premain方法执行时机:
-
agentmain方法:
# 启动时使用 -javaagent 参数 java -javaagent:agent.jar=args -jar app.jar # 时间线演示 JVM 启动 ↓ 检测到 -javaagent 参数 ↓ 加载 agent.jar ↓ 查找 MANIFEST.MF 中的 Premain-Class ↓ 调用 premain("args", instrumentation) // ← 在这里执行! ↓ 继续 JVM 启动流程 ↓ 调用应用的 main() 方法 ↓ 应用开始运行 -
premain方法:
# 应用已在运行,通过 Attach API 动态加载 java -jar app.jar # 先启动应用,无论有没有【-javaagent 参数】都行 ↓ (应用运行中...) java -jar agent-loader.jar <pid> agent.jar args # 动态附加 # 时间线演示 应用正常运行中... ↓ (运行了 5分钟、5小时、5天...) 用户执行 Attach 命令 ↓ VirtualMachine.attach(pid) ↓ vm.loadAgent("agent.jar", "args") ↓ 目标 JVM 中调用 agentmain("args", instrumentation) // ← 在这里执行! ↓ 应用继续运行,无中断
Premain:预防性的,在问题发生前准备好工具
Agentmain:治疗性的,在问题发生时紧急处理
| 方面 | Premain | Agentmain |
|---|---|---|
| 触发时机 | JVM 启动时 | JVM 运行时 |
| 执行顺序 | 在 main() 之前 | 在应用运行中 |
| 加载方式 | 静态加载 | 动态附加 |
agent相当于一个工具,要实现热更,有2种策略。
-
agentmain方法,针对没有配置-javaagent:的进程,可以直接java -jar agent-loader.jar
mu-agent-1.0.0.jar后,就能热更,无需启动 -
premain方法+启动的时候配置-javaagent:的进程,可以直接热更
两种方式的对应关系
-javaagent:agent.jar=test→ 调用premain("test", inst)loadAgent("agent.jar", "dynamic-test")→ 调用agentmain("dynamic-test", inst)
两者可以加载同一个agent.jar,只是:
-
调用时机不同:启动时 vs 运行时
-
入口方法不同:premain vs agentmain
-
处理重点不同:预防性转换 vs 运行时修复
1.启动时加载 (-javaagent)
java -javaagent:agent.jar=test -jar app.jar
↓
执行 agent.jar 中的 premain("test", instrumentation)
2.运行时加载 (Attach API)
VirtualMachine vm = VirtualMachine.attach(pid);
vm.loadAgent("agent.jar", "dynamic-test");
↓
执行 agent.jar 中的 agentmain("dynamic-test", instrumentation)
Agent 使用示例
Agent 类示例
package com.example;
public class HotFixAgent {
private static Instrumentation INST;
// premain - 静态加载入口
public static void premain(String agentArgs, Instrumentation inst) {
INST = inst;
System.out.println("Agent 静态加载: " + agentArgs);
}
// agentmain - 动态加载入口
public static void agentmain(String agentArgs, Instrumentation inst) {
INST = inst;
System.out.println("Agent 动态加载: " + agentArgs);
}
// 业务方法
public static void doHotFix(String className, byte[] bytes) {
// 热修复逻辑
}
}
Maven 配置
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<archive>
<manifestEntries>
<Premain-Class>com.example.HotFixAgent</Premain-Class>
<Agent-Class>com.example.HotFixAgent</Agent-Class>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
<Can-Attach>true</Can-Attach>
</manifestEntries>
</archive>
</configuration>
</plugin>
打包后测试
检查 JAR 包配置
# 验证 MANIFEST.MF 是否正确
jar tf my-agent.jar | grep META-INF/MANIFEST.MF
jar xf my-agent.jar META-INF/MANIFEST.MF
cat META-INF/MANIFEST.MF
测试 premain
# 测试静态加载
java -javaagent:my-agent.jar=test -version
# 应该看到: "Agent 静态加载: test"
测试 agentmain
# 初始状态:进程12000
java -jar myapp.jar # 普通Java应用,无任何agent相关代码
↓
PID: 12000 正常运行中...
# 动态附加过程
java -jar agent-loader.jar 12000 agent.jar args
↓
1. agent-loader 连接到进程12000
2. 将 agent.jar 注入到进程12000的内存中
3. 进程12000 加载并执行 agent.jar 中的 agentmain 方法
4. 进程12000 获得 Instrumentation 能力
# 最终状态:进程12000
# 现在拥有了 Instrumentation,可以进行字节码增强等操作
// 用法: java -jar agent-loader.jar <pid> <agent-jar-path> [args]
// agent-loader.jar:负责连接到目标进程并加载agent
// agent.jar:包含要在目标进程中执行的逻辑
// java -jar agent-loader.jar pid agent.jar args
// agent.jar包:1.需要有方法签名规则(agentmain 方法) 2.MANIFEST.MF 配置规则
// 下面就是agent-loader.jar代码,测试动态加载 : 连接到目标进程,目标进程加载agent.jar,目标进程执行agent.jar的agentmain 方法
public class TestAttach {
public static void main(String[] args) throws Exception {
String pid = args[0];
VirtualMachine vm = VirtualMachine.attach(pid);
vm.loadAgent("agent.jar", "dynamic-test");
vm.detach();
}
}
// 用法2: java -jar agent-all.jar <pid>
// 可以只用一个jar包,自己启动后加载自己
public class TestAgent {
public static void main(String[] args) throws Exception {
String pid = args[0];
VirtualMachine vm = VirtualMachine.attach(pid);
vm.loadAgent("agent-all.jar", "dynamic-test");
vm.detach();
}
}
// 自己agent-all.jar要有:1.需要有方法签名规则(agentmain 方法) 2.MANIFEST.MF 配置规则
浙公网安备 33010602011771号