一、简介

1.1、概述

Java Agent 又称为Java 探针
Java Agent 于 JDK1.5 引入。
Java Agent 类似于拦截器,主要功能

  • 在 Java 虚拟机启动执行 Java 类编译后的字节码前,提前获取到字节码信息,在字节码被执行前修改代码,实现代码增强。
  • 在 JVM 运行期间,修改已经加载的字节码信息

其主要的应用场景有

  • IDEA 破解
  • 热部署
  • 链路追踪工具
  • 应用监控
  • 诊断工具

Java Agent 代码不能独立运行,需要依附于目标程序的 JVM 进程,通过 -javaagent参数指定。

1.2、相关术语

JVMTI

JVMTI(JVM Tool Interface)是 JVM 对外提供给用户扩展的接口集合。
JVMTI 基于事件驱动,JVM 每执行一定的逻辑就会触发相关事件回调。
JVMTI回调机制,是实现如:DebuggerProfilerMonitorThread Analyser的基础

JVMTIAgent

JVMTIAgent 是一个动态库,JVMTI 接口实现,提供了一些常规无法实现的功能,为了和普通动态库区分,一般会实现一个或者多个函数,如:

  • Agent_OnLoad函数
    如果agent是在启动时加载的,通过JVM参数设置
  • Agent_OnAttach函数
    如果agent不是在启动时加载的,而是我们先attach到目标进程上,然后给对应的目标进程发送load命令来加载,则在加载过程中会调用Agent_OnAttach函数
  • Agent_OnUnload函数
    在agent卸载时调用

    javaagent/JPLISAgent

    javaagent 也是动态库,依赖于instrument的JVMTIAgent

    instrument

    instrument动态库实现,基于 JVMTIAgent
    instrument实现了 Agent_onLoadAgent_onAttach两方法。
    即,instrument提供了

  • 启动加载时的字节码增强
    通过 -javaanget:jar的方式

  • 运行时的字节码增强
    借助 JVM Attach 机制

    JVM Attach

    JVM Attach是 JVM 提供的一种进程间通信的功能,能让一个进程传命令给另一个进程,并进行一些内部的操作,比如进行线程 dump,那么就需要执行 jstack 进行,然后把 pid 等参数传递给需要 dump 的线程来执行。

    1.2、原理

    Java Agent 类似于拦截器,提供了两种拦截功能

  • 1、在字节码执行前,即 main函数 执行前,执行 premain函数,对字节码信息进行增强

  • 2、通过 JVM Attach 实现,在程序运行期间,执行 agentmain函数对字节码进行修改增强

    二、简单案例

    agent-demo.rar.txt

    2.1、项目代码

  • agent-impl:实现 agent 功能

  • agent-test:测试 agent 功能

    2.2、agent-test 基础代码

    image.png

    2.3、agent-impl 实现

    premain 函数

    实现程序运行前的代码增强
    image.png
    其中 RuntimeTransformer实现 ClassFileTransformertransform方法,进行代码增强处理。

    agentmain 函数

    实现程序运行期间的代码增强
    image.png
    其中 RuntimeTransformer实现 ClassFileTransformertransform方法,进行代码增强处理。

    RuntimeTransformer 代码增强具体实现

    单纯对特定的类进行打印处理,指定 agent-test项目中的 SpringApplication类进行处理。
    image.png

    添加 maven 插件,用来指定 Agent 相关类

    image.png

    2.4、测试

    premain 函数测试

    运行 agent-test测试代码 Application#main指定 -javaanget参数,指定相关的 agent-impljar包。
    image.png
    测试结果
    image.png

    agentman 函数测试

    无需增加 JVM 参数,直接通过代码的方式运行。
    agent-test项目中的 Application#main方法中追加代码:
    image.png
    如果无法引用到 VirtualMacineDescriptor,需要手动引入依赖
    image.png