Java Agent(一)、 初步认识Instrumentation

目录

1、什么是Instrumentation?

2、底层机制

2.1、工作流程

2.2、Instrumentation API

3、加载Java Agent

3.1、静态Agent示例

3.1.1、定义一个agent

3.1.2、配置 MANIFEST.MF

3.1.3、定义main测试类

3.1.4、启动参数添加-javaagent

3.2、动态Agent示例

3.2.1、定义一个动态 Agent

3.2.2、配置 MANIFEST.MF

3.2.3、定义main测试类

3.2.4、运行结果

3.2.5、总结遇到的问题

4、总结


1、什么是Instrumentation?

Instrumentation 是 Java SE 5 引入的一套 API,它允许开发者在运行时修改类的字节码。Java Instrumentation 可以实现在方法插入额外的字节码从而达到收集使用中的数据到指定工具的目的。Java.lang.instrument包的最大功能就是可以在已有的类上附加(修改)字节码来实现增强的逻辑,它最常见的用途包括:

  1. 性能监控:对代码进行埋点,以收集性能指标。
  2. 代码注入:动态添加方法或修改方法的行为。
  3. 内存分析:通过 Instrumentation 获取对象的大小等信息。

Instrumentation 通常与 Java Agent 一起使用。Java Agent 是一种特殊的 Java 应用,它会在目标应用启动时被 JVM 加载。Agent 通过实现 java.lang.instrument.Instrumentation 接口,来对字节码进行修改或增强。

2、底层机制

Instrumentation 的底层核心在于 JVM Tool Interface (JVMTI)。JVMTI 是 JVM 提供的一组 native 方法,算是JVM暴露出来的一些供用户扩展的接口集合,它允许外部工具与 JVM 进行交互(基于事件驱动,JVM指定到每一层逻辑层都会调用事件的回调接口)。通过 JVMTI,我们可以实现对 JVM 的监控、调试和修改。Instrumentation 就是利用 JVMTI 来实现对字节码的动态修改。

在Java 1.5开始引入Instrument增加技术时,最常用的一种使用方式是通过JVM启动参数:-javaagent来启动,这实际上是一种静态的代理。这种静态的agent只能在jar包启动时候进行代理,存在较大的局限性。Java 1.6开始引入了动态的Attach方式,可以在JVM启动之后的任意时刻通过Attach API远程加载Agent的jar,比如阿里开源的arthas工具就是基于Attach API实现的。

2.1、工作流程

Instrumentation 的实现依赖以下 JVM 的特性和机制:

  1. 类加载机制:JVM 在加载类时,经过类加载器(ClassLoader)和类验证器(Verifier),将 .class 文件中的字节码加载到内存中。Instrumentation 能够在类加载前修改字节码,或者重新定义已加载的类。
  2. Instrumentation 接口:JVM 在启动时会初始化一个 InstrumentationImpl 对象,并将其传递给 Java Agent 的 premain 方法或 agentmain 方法。这个对象负责与 JVM 内部的类加载机制交互。
  3. 字节码转换:Instrumentation 的关键是 ClassFileTransformer 接口,通过该接口,开发者可以拦截类加载过程,并对字节码进行修改。
  4. 类重定义与重新转换:Instrumentation 提供了 redefineClasses 和 retransformClasses 方法,分别用于重新定义和重新转换类的字节码。

Instrumentation 的工作流程大致如下:

  1. Agent 加载: JVM 启动时,通过 -javaagent 参数加载指定的 Agent jar 包。
  2. Agent 初始化: Agent 的 premain 方法被调用,此时 JVM 会传递一个 Instrumentation 实例给 Agent。
  3. 注册 ClassFileTransformer: Agent 通过 Instrumentation.addTransformer() 方法注册一个 ClassFileTransformer。
  4. 类加载: 当类加载器加载某个类时,JVM 会调用注册的 ClassFileTransformer 的 transform 方法。
  5. 字节码修改: ClassFileTransformer 可以对字节码进行修改,然后返回修改后的字节码。
  6. 类定义: JVM 使用修改后的字节码来定义类。

2.2、Instrumentation API

我这里使用的是JDK 21版本,Instrumentation类包含在java.instrument模块中。

package java.lang.instrument;

import java.security.ProtectionDomain;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarFile;

public interface Instrumentation {
    /**
     * JDK1.6引入的方法,注册一个ClassFileTransformer,用于在类加载时对字节码进行转换。
     * 当类加载器加载某个类时,JVM 会调用注册的 ClassFileTransformer 的 transform 方法,允许我们对字节码进行修改。
     */
    void addTransformer(ClassFileTransformer transformer, boolean canRetransform);
    void addTransformer(ClassFileTransformer transformer);
    boolean removeTransformer(ClassFileTransformer transformer);
    boolean isRetransformClassesSupported();

    /**
     * 重新转换已经加载的类,允许我们对已经加载到 JVM 中的类进行再次转换,实现热部署等功能。
     * 这个方法是JDK 1.6引入的,这个方法很经常会被使用到。
     */
    void retransformClasses(Class<?>... classes) throws UnmodifiableClassException;
    boolean isRedefineClassesSupported();

    /**
     * 使用新的字节码重新定义一组类。经常使用在动态加载类,或类替换
     */
    void redefineClasses(ClassDefinition... definitions) throws  ClassNotFoundException, UnmodifiableClassException;
    boolean isModifiableClass(Class<?> theClass);

    /**
     *  获取所有已加载的类。
     */
    @SuppressWarnings("rawtypes")
    Class[] getAllLoadedClasses();

    /**
     * 获取由当前类加载器初始化的类。
     */
    @SuppressWarnings("rawtypes")
    Class[]  getInitiatedClasses(ClassLoader loader);

    long getObjectSize(Object objectToSize);
    void appendToBootstrapClassLoaderSearch(JarFile jarfile);
    void appendToSystemClassLoaderSearch(JarFile jarfile);
    boolean isNativeMethodPrefixSupported();
    void setNativeMethodPrefix(ClassFileTransformer transformer, String prefix);

    /**
     * 在 Java 9 中,为了更好地支持模块化系统,Instrumentation 接口新增了一个重要方法:
     * redefineModule。这个方法允许我们在运行时对已加载的模块进行重新定义。
     */ 
    void redefineModule(Module module,
                        Set<Module> extraReads,
                        Map<String, Set<Module>> extraExports,
                        Map<String, Set<Module>> extraOpens,
                        Set<Class<?>> extraUses,
                        Map<Class<?>, List<Class<?>>> extraProvides);

    boolean isModifiableModule(Module module);
}

3、加载Java Agent

大致了解了其实现原理后,我们来初步使用下。Java Instrumentation通常有2种方式可以加载Java Agent:

  1. 静态代理,在JVM启动时通过-javaagent 选项加载代理,适用于需要在应用启动阶段插入逻辑的场景,如性能监控工具。其实现原理:
    1. 静态代理需要在启动时定义一个包含 premain 方法的 Java Agent。
    2. JVM 在启动时会调用 premain 方法,并传入 Instrumentation 实例。
  2. 动态代理,在应用程序运行过程中,通过 Attach API 动态加载,适用于需要在运行时动态注入逻辑的场景,如调试工具或热部署。其实现原理:
    1. 动态代理使用 JVM 提供的 Attach API(com.sun.tools.attach 包)。
    2. 动态代理的 Agent 需要实现 agentmain 方法,JVM 会在 Attach 时调用它。

3.1、静态Agent示例

3.1.1、定义一个agent

package org.example.instrument.static_load;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;

public class MyAgent {

    public static void premain(String agentArgs, Instrumentation inst) {
        System.out.println("---- begin load agent ----:Static Agent loaded with args: " + agentArgs);
        // 添加 Transformer
        inst.addTransformer(new ExampleTransformer());
    }


    // 定义一个字节码转换器
    static class ExampleTransformer implements ClassFileTransformer {
        @Override
        public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
                                ProtectionDomain protectionDomain, byte[] classfileBuffer) {
            if (className.equals("org/example/instrument/static_load/MyDemo")) {
                System.out.println("---- Transforming class: " + className);
                // 这里可以修改字节码,示例中直接返回原字节码
                return classfileBuffer;
            }
            return classfileBuffer;
        }
    }

}

3.1.2、配置 MANIFEST.MF

集成maven,我这里使用的是maven3.9.9版本。其中maven-jar-plugin使用的是3.4.2版本,注意maven插件不同,配置属性项也不同,需要根据版本按需修改。

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-jar-plugin</artifactId>
            <configuration>
                <archive>
                    <manifestEntries>
                        <Premain-Class>org.example.instrument.static_load.MyAgent</Premain-Class>
                    </manifestEntries>
                </archive>
            </configuration>
        </plugin>
    </plugins>
</build>

3.1.3、定义main测试类

public static void main(String[] args) {
    System.out.println("hello static agent");
}

3.1.4、启动参数添加-javaagent

-javaagent:E:\idea_projects\java-agent-demo\target\java-agent-demo-1.0-SNAPSHOT.jar=agentArgs1

这里的java-agent-demo-1.0-SNAPSHOT.jar是上面maven package出来的包路径和名称,这里没有做修改直接拿来测试使用,agentArgs1为传入agent的参数。

运行效果如下:

3.2、动态Agent示例

3.2.1、定义一个动态 Agent

和静态agent大同小异,只是入口方法premain改为了agentmain。

package org.example.instrument.dynamic_load;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;

public class MyDynamicAgent {

    public static void agentmain(String agentArgs, Instrumentation inst) {
        System.out.println("---- begin load agent ----:Static Agent loaded with args: " + agentArgs);
        // 添加 Transformer
        inst.addTransformer(new ExampleTransformer(), true);
    }


    // 定义一个字节码转换器
    static class ExampleTransformer implements ClassFileTransformer {
        @Override
        public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
                                ProtectionDomain protectionDomain, byte[] classfileBuffer) {
            if (className.equals("org/example/instrument/dynamic_load/MyDynamicDemo")) {
                System.out.println("---- Transforming class: " + className);
                // 这里可以修改字节码,示例中直接返回原字节码
                return classfileBuffer;
            }
            return classfileBuffer;
        }
    }

}

3.2.2、配置 MANIFEST.MF

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-jar-plugin</artifactId>
    <configuration>
        <archive>
            <manifestEntries>
                <Agent-Class>org.example.instrument.dynamic_load.MyDynamicAgent</Agent-Class>
                <Can-Redefine-Classes>true</Can-Redefine-Classes>
                <Can-Retransform-Classes>true</Can-Retransform-Classes>
            </manifestEntries>
        </archive>
    </configuration>
</plugin>

3.2.3、定义main测试类

package org.example.instrument.dynamic_load;

import com.sun.tools.attach.VirtualMachine;

public class MyDynamicDemo {

    public static void main(String[] args) {
        try {
            // 目标 JVM 的进程 ID(使用 jps 命令获取)。这里812进程号,是我另起了一个java进程的id
            String targetJvmPid = "812";

            // Attach 到目标 JVM
            VirtualMachine vm = VirtualMachine.attach(targetJvmPid);

            // 加载动态 Agent
            vm.loadAgent("E:\\idea_projects\\java-agent-demo\\target\\java-agent-demo-1.0-SNAPSHOT.jar", "dynamicAgentArgs1231");

            // Detach
            vm.detach();

            System.out.println("--- Agent attached successfully.");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

3.2.4、运行结果

直接运行MyDynamicDemo类,由于这里使用API,动态的将agent附加到另一个JVM进程上,因此无需像静态agent一样指定-javaagent参数。

agent附加成功:

目标JVM上打印:

3.2.5、总结遇到的问题

由于我这里使用的是JDK21版本,21版本已经将jdk.attach做为单独模块,因此开始很容易遇到找不到jdk.attach包。需要绑定jdk.attach模块。

4、总结

至此,我们已经初步认识了 Instrumentation,他是 JVM 提供的一个强大工具,通过它可以实现动态字节码修改、性能监控、热部署等功能。通过合理使用 Instrumentation,开发者可以大幅提升系统的动态性与可维护性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/934116.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

关于SpringBoot项目创建后构建总是失败的问题

第一个问题&#xff1a;IDEA创建项目总是失败 原因&#xff1a;创建项目的时候默认使用的是https://start.spring.io&#xff0c;这个是一个外国网站&#xff0c;众所周知的就是国内访问总是出现不稳定的现象&#xff0c;这就是导致项目创建失败的最终原因。 解决方法&#x…

Java-自动拆箱/装箱/缓存/效率

为什么基本类型需要包装类&#xff1f; 泛型与集合支持问题&#xff1a;基本数据类型在使用上虽然方便、简单且高效&#xff0c;但像泛型以及集合元素的存储等场景并不支持基本数据类型&#xff0c;而包装类可以解决这个问题&#xff0c;使其能更好地融入到一些需要对象类型的…

计算机毕业设计Python中华古诗词知识图谱可视化 古诗词智能问答系统 古诗词数据分析 古诗词情感分析模型 自然语言处理NLP 机器学习 深度学习

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

微服务网关SpringCloudGateway、Kong比较

网关产品 1. Spring Cloud Gateway 基本信息 Spring Cloud Gateway是Spring Cloud生态系统中的一个组件&#xff0c;基于Spring 5、Project Reactor和Spring Boot 2构建。它旨在为微服务架构提供一种简单而有效的API网关解决方案。 功能特点 路由功能强大&#xff1a;使用Rou…

实现基于分布式的LAMP架构+NFS实时同步到备份服务器

概述 项目计划用WordPress搭建一个博客系统, 为了性能更好,两个服务器都对外提供WordPress博客系统服务, 数据放在MySQL服务器, 有些上传的图片发送到NFS服务器上&#xff0c;并且把NFS数据实时同步到一个备份服务器上。 服务名称IP地址DNS10.0.0.200WEB110.0.0.201W…

【NVIDIA orin nx 安装ultralytics yolov11】

注意:不同用户安装的python可能会在不同的路径,因此不同的pip管理会导致安装的 torch和torchvision会在不同的路径下 记得区分用户来运行yolo 一、确认系统 JetPack 版本 此处使用5.1.1 1、查看JetPack 版本 jtop二、安装 ultralytics、pytorch、torchvision、onnxruntime…

Linux系统挂载exfat格式U盘教程,触觉智能RK3562开发板演示

本文介绍Linux系统&#xff08;Ubuntu/Debian通用&#xff09;挂载exfat格式U盘的方法&#xff0c;触觉智能RK3562开发板演示&#xff0c;搭载4核A53处理器&#xff0c;主频高达2.0GHz&#xff1b;内置独立1Tops算力NPU&#xff0c;可应用于物联网网关、平板电脑、智能家居、教…

【Vulkan入门】08-CreateRenderPass

目录 先叨叨git信息关键代码TestPipeLine::CreateRenderPass() 先叨叨 上篇已经为Pipeline编写好了程序&#xff08;Shader&#xff09;。接下来要为Pipeline创建RenderPass。 关于RenderPass&#xff0c;在【Vulkan入门】06-Pipeline介绍中已经作了简单的介绍。这里再详细说一…

从 HTTP 到 HTTPS 再到 HSTS

近些年&#xff0c;随着域名劫持、信息泄漏等网络安全事件的频繁发生&#xff0c;网站安全也变得越来越重要&#xff0c;也促成了网络传输协议从 HTTP 到 HTTPS 再到 HSTS 的转变。 HTTP HTTP&#xff08;超文本传输协议&#xff09; 是一种用于分布式、协作式和超媒体信息系…

01-Chromedriver下载与配置(mac)

下载地址&#xff1a; 这里我用的最后一个&#xff0c;根据自己chrome浏览器选择相应的版本号即可 ChromeDriver官网下载地址&#xff1a;https://sites.google.com/chromium.org/driver/downloads ChromeDriver官网最新版下载地址&#xff1a;https://googlechromelabs.git…

面试技术点之安卓篇

一、基础 二、高级 三、组件 Android中SurfaceView和TextureView有什么区别&#xff1f; 参考 Android中SurfaceView和TextureView有什么区别&#xff1f; 四、三方框架 五、系统源码 六、性能优化

架构13-持久化存储

零、文章目录 架构13-持久化存储 1、Kubernetes 存储设计 &#xff08;1&#xff09;存储设计考量 **设计哲学&#xff1a;**Kubernetes 遵循用户通过资源和声明式 API 描述意图&#xff0c;Kubernetes 根据意图完成具体操作。**复杂性&#xff1a;**描述用户的存储意图本身…

可视化建模以及UML期末复习----做题篇

一、单项选择题。&#xff08;20小题&#xff0c;每小题2分,共40分&#xff09; 1、UML图不包括&#xff08; &#xff09; A、用例图 B、状态机图 C、流程图 D、类图 E、通信图 答案&#xff1a;C、流程图 UML中不包括传统意义上的流程图&#xff0c;流程图通常是指B…

idea_maven详解

秒懂Maven maven简介maven安装和配置maven本地配置maven工程的GAVP创建maven工程项目结构说明项目构建说明 Maven依赖管理核心信息配置依赖管理配置依赖信息查询依赖范围设置依赖属性配置依赖下载失败错误解决Build构建配置依赖传递依赖冲突 maven工程继承继承作用应用场景继承…

vue3+vite纯前端实现自动触发浏览器刷新更新版本内容,并在打包时生成版本号文件

前言 在前端项目中&#xff0c;有时候为了实现自动触发浏览器刷新并更新版本内容&#xff0c;可以采取一系列巧妙的措施。我的项目中是需要在打包时候生成一个version.js文件&#xff0c;用当前打包时间作为版本的唯一标识&#xff0c;然后打包发版 &#xff0c;从实现对版本更…

鸿蒙实现应用通知

目录&#xff1a; 1、应用通知的表现形式2、应用通知消息的实现1、发布普通文本类型通知2、发布进度类型通知3、更新通知4、移除通知 3、设置通知道通展示不同形式通知4、设置通知组5、为通知添加行为意图1、导入模块2、创建WantAgentInfo信息3、创建WantAgent对象4、构造Notif…

2024 年 MySQL 8.0.40 安装配置、Workbench汉化教程最简易(保姆级)

首先到官网上下载安装包&#xff1a;http://www.mysql.com 点击下载&#xff0c;拉到最下面&#xff0c;点击社区版下载 windows用户点击下面适用于windows的安装程序 点击下载&#xff0c;网络条件好可以点第一个&#xff0c;怕下着下着断了点第二个离线下载 双击下载好的安装…

【RabbitMQ】RabbitMQ中核心概念交换机(Exchange)、队列(Queue)和路由键(Routing Key)等详细介绍

博主介绍&#xff1a;✌全网粉丝21W&#xff0c;CSDN博客专家、Java领域优质创作者&#xff0c;掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域✌ 技术范围&#xff1a;SpringBoot、SpringCloud、Vue、SSM、HTML、Nodejs、Python、MySQL、PostgreSQL、大数据、物…

【C++笔记】AVL树的深度剖析

【C笔记】AVL树的深度剖析 &#x1f525;个人主页&#xff1a;大白的编程日记 &#x1f525;专栏&#xff1a;C笔记 文章目录 【C笔记】AVL树的深度剖析前言一. AVL树的概念二.AVL树的实现2.1 AVL树的结构2.2 AVL树的插入2.3 平衡因子更新 三.旋转3.1旋转的原则3.2右单旋3.3左…

Ubuntu 环境安装 之 RabbitMQ 快速入手

Hi~&#xff01;这里是奋斗的明志&#xff0c;很荣幸您能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎 ~~ &#x1f331;&#x1f331;个人主页&#xff1a;奋斗的明志 &#x1f331;&#x1f331;所属专栏&#xff1a;RabbitMQ &#x1f4da;本系列文章为个人学…