JVM(6) Java Agent
2024-11-18 15:52:59 # Language # Java

1. Java Agent

Java Agent技术是JDK提供的用来编写Java工具的技术, 使用这种技术生成一种特殊的jar包, 这种jar包可以让Java程序运行其中的代码。

1.1 两种模式

Java Agent技术实现了让Java程序执行独立的Java Agent程序中的代码, 执行方式有两种: 静态加载模式、动态加载模式

静态加载

静态加载模式可以在程序启动的一开始就执行我们需要执行的代码, 适合用APM等性能监测系统从一开始就监控程序的执行性能。静态加载模式需要在Java Agent的项目中编写一个premain的方法, 并打包成jar包。

1
public static void premain(string agentArgs, Instrumentation inst)
1
2
# 使用java工具
java -javaagent:./agent.jar -jar test.jar

premain方法会在主线程中执行

动态加载

动态加载模式可以随时让java agent代码执行, 适用于Arthas等诊断系统。动态加载模式需要在Java Agent的项目中编写一个agentmain的方法, 并打包成jar包。

1
public static void agentmain(String agentArgs, Instrumentation inst)

接下来使用以下代码就可以让java agent代码在指定的java进程中执行了

1
2
VirtualMachine vm = virtualMachine.attach("24200"); //动态连接到24200进程ID的java程序
vm.loadAgent("itheima-jvm-java-agent-jar-with-dependencies.jar"); //加载java agent

agentmain方法会在独立线程中执行

1.2 搭建环境

静态加载模式

  1. 创建maven项目, 添加maven-assembly-plugin插件, 此插件可以打包出java agent的jar包
  2. 编写类和premain方法, premain方法中打印一行信息
  3. 编写MANIFEST.MF文件, 此文件主要用于描述java agent的配置属性, 比如使用哪一个类的premain方法
  4. 使用maven-assembly-plugin进行打包
  5. 创建spring boot应用, 并静态加载上一步打包完的java agent

动态加载模式

  1. 创建maven项目, 添加maven-assembly-plugin插件, 此插件可以打包出java agent的jar包
  2. 编写类和agentmain方法, agentmain方法中打印一行信息
  3. 编写MANIFEST.MF文件, 此文件主要用于描述java agent的配置属性, 比如使用哪一个类的agentmain方法
  4. 使用maven-assembly-plugin进行打包
  5. 编写main方法, 动态连接到运行中的java程序

2. 简化版Arthas

编写一个简化版的Arthas程序, 具备以下几个功能:

  • 查看内存使用情况
  • 查看直接内存使用情况, 生成堆内存快照
  • 打印栈信息
  • 打印类加载器
  • 打印类的源码
  • 打印方法执行的参数和耗时

2.1 获取运行时信息-JMX

JDK从1.5开始提供了Java Management Extensions(JMX)技术, 通过Mbean对象的写入和获取, 实现:

  • 运行时配置的获取和更改
  • 应用程序运行信息的获取(线程栈、内存、类信息等)

获取JMX默认提供的Mbean可以通过如下的方式, 例如获取内存信息: ManagementFactory.getMemoryPoolMXBeans()

ManagementFactory提供了一系列方法获取各种信息

image-20241114165945286

更多的信息可以通过 ManagementFactory.getPlatformMXBeans() 获取

1
2
3
4
5
// 获取Java虚拟机中分配的直接内存和内存映射缓冲区的大小
Class bufferPoolMXBeanC lass = Class.forName("java.lang.management.BufferPoolMXBean");
List<BufferPoolMxBean> bufferPoolMXBeans = ManagementFactory.getPlatformMXBeans(bufferPoolMXBeanclass);
// 获取虚拟机诊断用的MXBean, 生成内存快照
HotspotDiagnosticMXBean hotspotDiagnosticMXBean = ManagementFactory.getPlatformMXBean(HotSpotDiagnosticMxBean.class);

2.2 获取类和类加载器信息-Instumentation对象

Java Agent中可以获得Java虛拟机提供的Instumentation对象

该对象有以下几个作用:

  1. redefine, 重新设置类的字节码信息
  2. retransform, 根据现有类的字节码信息进行增强
  3. 获取所有已加载的类信息

https://docs.oracle.com/en/java/javase/17/docs/api/java.instrument/java/lang/instrument/Instrumentation.html

打印类的源码

  1. 获得内存中的类的字节码信息。利用Instrumentation提供的转换器来获取字节码信息

    定义转换器, 实现 ClassFileTransformer 接口

  2. 通过反编译工具将字节码信息还原成源代码信息

    使用jd-core依赖库

2.3 打印方法执行的参数和耗时-ASM

Spring AOP 可以实现统计方法执行时间, 打印方法参数等功能, 但是使用这种方式存在几个问题:

  • 代码有侵入性, AOP代码必须在当前项目中被引入才能完成相应的功能
  • 无法做到灵活地开启和关闭功能。
  • 与Spring框架强耦合, 如果项目没有使用Spring框架就不可以使用。

使用Java Agent + 字节码增强技术, 就可以解决上述三个问题。字节码增强框架是在当前类的字节码信息中插入一部分字节码指令, 从而起到增强的作用。

ASM是一个通用的Java字节码操作和分析框架。它可用于直接以二进制形式修改现有类或动态生成类。ASM重点关注性能。让操作尽可能小且尽可能快, 所以它非常适合在动态系统中使用。ASM的缺点是代码复杂。

2.4 打印方法执行的参数和耗时-Byte Buddy

Byte Buddy 是一个代码生成和操作库, 用于在 Java 应用程序运行时创建和修改 Java 类, 而无需编译器的帮助。Byte Buddy底层基于ASM, 提供了非常方便的 API。

3. APM系统的数据采集

Application performance monitor (APM) 应用程序性能监控系统是采集运行程序的实时数据并使用可视化的方式展示, 使用APM可以确保系统可用性, 优化服务性能和响应时间, 持续改善用户体验。常用的APM系统有Apacheskywalking、Zipkin等。

需求: 编写一个简化版的APM数据采集程序,具备以下几个功能

  1. 无侵入性获取spring boot应用中,controller层方法的调用时间。
  2. 将所有调用时间写入文件中。

一般程序启动之后就需要持续地进行信息的采集,所以采用静态加载模式

3.1 Java Agent参数获取

在Jaya Agent中,可以通过以下方式传递参数:

  • java -javaagent:./agent.jar=参数 -jar test.jar
  • java -javaagent:./agent.jar=paraml=valuel,param2=value2 -jar test.jar 在Java代码中使用字符串解析出对应的key value

接下来通过premain参数中的agentArgs字段获取

1
public static void premain(String agentArgs, Instrumentation inst) {}