写点什么

1000 个字带你一次性搞懂 JavaAgent 技术,反正我是彻底服了

作者:程序员啊叶
  • 2022 年 7 月 27 日
  • 本文字数:1467 字

    阅读完需:约 5 分钟

1000个字带你一次性搞懂JavaAgent技术,反正我是彻底服了

JavaAgent 技术

JavaAgent 是一种特殊的 Java 程序,是 Instrumentation 的客户端。它与普通 Java 程序通过 main 方法启动不同,JavaAgent 并不是一个可以单独启动的程序,它必须依附在一个 Java 应用程序(JVM)上,与主程序运行在同一个进程中,通过 Instrumentation API 与虚拟机交互。

JVM 启动时静态加载

对于 JVM 启动时加载的 Agent 模块代码,Instrumentation 会通过 premain 方法传入代理程序,premain 方法会在调用程序 main 方法之前被调用,同时 Instrumentation 包含 agentmain 方法实现字节码改写,二者的区别如下:

● premain 方 法 用 于 在 启 动 时 , 在 类 加 载 前 定 义 类 的 TransFormer(转化器),在类加载的时候更新对应的类的字节码。

● agentmain 方法用于在运行时进行类的字节码的修改,步骤分为注册类的 TransFormer 调用和 retransformClasses 函数进行类的重加载。

premain 方法与 agentmain 方法相比有很大的局限性。premain 方法仅限于应用程序的启动时,即 main 函数执行前。此时还有很多类没有被加载,而这些类使用 premain 方法是无法实现字节码改写的。

目前,主流的基于探针的监控系统都是基于这种方式实现的对应用的无侵入监控。我们知道程序的入口是 main 方法,而 premain 方法代表了在程序正式启动之前执行的动作,它同时具备类似 AOP 的能力。

Transformer 提供字节码文件流转化的能力,如下图所示是 Class 文件转换图。

字节码改写

如上图所示,任何 Class 文件在加载时,都要经过 premain 这一代码转换环节。通过一系列的 TransFormer 转换,Class 字节码文件流最终转变为我们期望的代码实现,然后被加载到 JVM 中。修改 Class 字节码文件流的动作是在 Transformer 中进行的。我们可以使用 Javaassist 技术修改字节码文件流(下一节介绍)。下面就是我们实现的一个类 , 实 现 了 带 Instrumentation 参 数 的 premain 方 法 。 调 用 addTransformer 方法对启动时所有的类进行拦截,示例代码如下:


JVM 启动后动态 Instrument 机制

关于 JVM 启动后动态加载 Agent 的方法,Instrumentation 会通过 agentmain 方法传入程序。agentmain 方法在 main 函数开始运行后才被调用,其最大优势是可以在程序运行期间进行字节码的替换。

Attach API[1]实现动态注入的原理如下。

你的应用程序通过虚拟机提供的 attach(pid)方法,可以将代理程序连接(attach)到一个运行中的 Java 进程上,之后便可以通过 loadAgent(AgentJarPath)将 Agent 的 jar 包注入对应的进程,然后对应的进程会调用 agentmain 方法,如下图所示。


工程结构和上面 premain 的一样,编写 AgentMainTest 代码示例如下:


JavaAgent 运行前启动加载代理程序的方法如下。

JavaAgent 有两个启动时机,一个是在程序启动时通过-javaAgent 参数启动代理程序;另一个是在程序运行期间通过 Java Tool API 中的 Attach API 动态启动代理程序。我们通过-javaAgent 来指定我们编写的 Agent 的 jar 路径(./{Location}/Agent.jar)。这样在启动时,Agent 就可以做定制化的字节码改动了。对于 Spring Boot 类内置容器的服务,可以使用下面方式:


在 Tomcat 启动时,它会读取 CATALINA_OPTS 环境变量,并将它加入启动命令中。在环境变量中添加如下信息:


Java 程序运行后加载代理的方法如下。

程序启动之后,我们通过某种特定的手段加载 Java Agent。这个特定的手段就是虚拟机的 Attach API。这个 API 其实是 JVM 进程之间的沟通桥梁,它的底层通过 Socket 进行通信。JVM A 可以发送一些指令给 JVM B,JVM B 收到指令之后,可以执行对应的逻辑,比如在命令行中经常使用的 jstack、jcmd、jps 等命令。因为是进程间通信,所以使用 Attach API 的也是一个独立的 Java 进程。下面是一个简单的实现,代码示例如下:


用户头像

还未添加个人签名 2022.07.13 加入

还未添加个人简介

评论

发布
暂无评论
1000个字带你一次性搞懂JavaAgent技术,反正我是彻底服了_Java_程序员啊叶_InfoQ写作社区