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,除非你有非常特殊的定制化需求!

使用规则

  1. 方法签名规则

    // 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)
    
  2. 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)
    

规则总结

  1. 方法名必须准确premain / agentmain
  2. 方法签名必须正确public static void
  3. MANIFEST.MF 必须配置Premain-Class / Agent-Class
  4. 权限必须声明Can-Redefine-Classes
  5. 打包必须包含清单: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种策略。

  1. agentmain方法,针对没有配置-javaagent:的进程,可以直接java -jar agent-loader.jar mu-agent-1.0.0.jar后,就能热更,无需启动

  2. 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 配置规则
posted @ 2025-11-13 15:07  deyang  阅读(7)  评论(0)    收藏  举报