• IntelliJ IDEA的调试功能
  • 热部署,比如 JRebel
  • 线上诊断,比如 Btrace/Greys/Arthas
  • 性能分析,比如 Visual VM/JConsole

背后隐藏的力量竟然都来自于本文主角!

Java Agent是一个 jar 包,只不过这个 jar 包不能独立运行,需要依附到我们的目标 JVM 进程。他其实有两种叫法:

  • 代理:比方需要了解目标 JVM 的一些运行指标,就可以通过 Java Agent 来实现,这样看来它就是一个代理的效果,我们最后拿到的指标是目标 JVM ,但是我们是通过 Java Agent 来获取的,对于目标 JVM 来说,它就像是一个代理
  • 探针:JVM 一旦跑起来,对于外界来说,它就是一个黑盒。而 Java Agent 可以像一支针一样插到 JVM 内部,探到我们想要的东西,并且可以注入东西进去。

比如 IDEA 调试器,开启调试功能后,在 debugger 面板中可以看到当前上下文变量的结构和内容,还可以在 watches 面板中运行一些简单的代码,比如取值赋值等操作。还有 Btrace、Arthas 这些线上排查问题的工具,比方说有接口没有按预期的返回结果,但日志又没有错误,这时,我们只要清楚方法的所在包名、类名、方法名等,不用修改部署服务,就能查到调用的参数、返回值、异常等信息。

热部署功能那就不仅仅是探测这么简单了。热部署即在不重启服务的情况下,保证最新的代码逻辑在服务生效。修改某类后,通过 Java Agent 的 instrument 机制,把之前的字节码替换为新代码所对应的字节码。

Java Agent 结构

Java系统Debug/热部署/程序监控/性能分析一把梭的Java Agent_java

Java Agent 最终以 jar 包的形式存在。主要包含两个部分,一部分是实现代码,一部分是配置文件。

配置文件放在 META-INF 目录下,文件名为 MANIFEST.MF 。包括以下配置项:

  • Manifest-Version: 版本号
  • Created-By: 创作者
  • Agent-Class: agentmain 方法所在类
  • Can-Redefine-Classes: 是否可以实现类的重定义
  • Can-Retransform-Classes: 是否可以实现字节码替换
  • Premain-Class: premain 方法所在类

入口类实现 agentmain 和 premain 两个方法即可,方法要实现什么功能就由你的需求决定了。

Java Agent 实现和使用

实现简单的 Java Agent,Java 1.8,主要实现:

  • 打印当前加载的所有类的名称
  • 监控一个特定的方法,在方法中动态插入简单的代码并获取方法返回值

在方法中插入代码主要是用到字节码修改技术,字节码修改技术主要有 javassist、ASM,已经 ASM 的高级封装可扩展 cglib,例用javassist。

<dependency>
    <groupId>org.javassist</groupId>
    <artifactId>javassist</artifactId>
    <version>3.30.2-GA</version>
</dependency>

实现入口类和功能逻辑

入口类,要实现 agentmain 和 premain 两个方法。这两个方法的运行时机不一样。这要从 Java Agent 的使用方式来说了,Java Agent 有两种启动方式:

JVM 启动参数 -javaagent:xxx.jar 随 JVM 一起启动,这时会调用premain方法,并在主进程的 main方法之前执行:

Java系统Debug/热部署/程序监控/性能分析一把梭的Java Agent_Java_02

以 loadAgent 方法动态 attach 到目标 JVM 上,会执行agentmain:

Java系统Debug/热部署/程序监控/性能分析一把梭的Java Agent_JVM_03