Java Agent技术

这篇具有很好参考价值的文章主要介绍了Java Agent技术。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

在定位公司问题的时候,需要了解一下skywalking的相关知识,而agent就提上了日程。

官网文档
Agent技术是Jdk在1.5版本之后,所提供的一个在jvm启动前后对部分java类代理加强的机制。由于是直接修改字节码,并不会对业务代码有注入,所以可以很好的应用于监控或者热部署等场景。
正常所提到的Agent一般都是部署成jar包的样子,比如agent-1.0-SNAPSHOT.jar。
在这个jar包中,要添加一个MANIFEST.MF文件,在文件中指定jar包的代理类,比如下面代码中的Premain-Class。
在对应的代理类,要实现一个permain方法或者agentmain方法,这样jvm可以通过MANIFEST找到类,通过类再找到对应的方法,从而进行加强,所以加强逻辑是在permain方法或者agentmain方法内部实现的。

Manifest-Version: 1.0
Built-By: qisi
Premain-Class: com.qisi.agent.InterviewAgent
Agent-Class: com.qisi.agent.InterviewAgent
Can-Redefine-Classes: true
Can-Retransform-Classes: true
Class-Path: byte-buddy-1.10.22.jar
Created-By: Apache Maven 3.8.1
Build-Jdk: 1.8.0_332
public class InterviewAgent {
    public static void premain(String agentArgs, Instrumentation instrumentation) {
    }
    public static void agentmain(String agentArgs, Instrumentation instrumentation) {
    }
}

而如果在permain或者agentmain方法打上debug可以发现,执行时是通过sun.instrument.InstrumentationImpl#loadClassAndCallPremain和sun.instrument.InstrumentationImpl#loadClassAndCallAgentmain两个方法通过反射来执行到我们指定的类的。
Agent技术有两种场景,一种是在jvm启动之前,通过-javaagent:path来指定jar包,像是skywalking就是采用的这种方式;另一种则是在jvm启动之后,通过attach指定的进程,对jvm中的类进行加强,arthas就是采用的这种方式。
在具体介绍这两种方式之前,需要先讲一下Instrumentation相关类和接口

java.lang.Instrumentation

Instrumentation

Instrumentation相关的类都在java.lang.Instrumentation包下,两个异常,两个接口,一个类。
Java Agent技术
两个异常在这里不做介绍,功能就像类名一样。核心的其实是Instrumentation接口,本文仅关注红框内的几个方法。这几个方法都是通过permain和agentmain获取到的instrumentation实例进行的操作。
Java Agent技术
从时间发展来看,其中jdk1.5开始支持的是下面几个方法,也就是说在jdk5的时候,仅支持添加和移除类转换器,且添加的类转换器只能在加载和重定义的时候使用。就是说如果类没有加载,那么通过addTransformer方法注册的ClassFileTransformer就可以对这个类进行增强,否则一旦类已经加载完毕,则只能通过redefineClasses,完全替换类定义再次触发loadClass来增强

addTransformer(ClassFileTransformer transformer)
removeTransformer(ClassFileTransformer transformer)
isRedefineClassesSupported();//依赖于MANIFEST中的Can-Redefine-Classes值
redefineClasses(ClassDefinition... definitions)

而从jdk1.6开始,增加了一个retransformClasses的概念。retransform和redefine的区别,前者是在原有类的基础上进行修改,后者则是完全重定义,不使用原有类做任何参考。
需要注意的事,只有在首次调用addTransformer时,将canRetransform设置为true的类,才可以被重新转换。

addTransformer(ClassFileTransformer transformer, boolean canRetransform);
isRetransformClassesSupported();
retransformClasses(Class<?>... classes)//依赖于MANIFEST中的Can-Retransform-Classes值
isModifiableClass(Class<?> theClass);

ClassFileTransformer、ClassDefinition

这两个类其实都是Instrumentation接口方法的入参,其中用的比较多的应该是ClassFileTransformer。这个类只有一个transform,jvm类加载的时候都会调用一遍这个方法。如果需要加强,那么就利用给定的参数,进行字节码的改动,将改动后的字节码作为返回值返回;如果无需增强,则直接返回null即可。

byte[]
transform(  ClassLoader         loader,
            String              className,
            Class<?>            classBeingRedefined,
            ProtectionDomain    protectionDomain,
            byte[]              classfileBuffer)

ClassDefinition也类似,不过是在对象里重新绑定class和byte的关系

public final class ClassDefinition {
    /**
     *  The class to redefine
     */
    private final Class<?> mClass;

    /**
     *  The replacement class file bytes
     */
    private final byte[]   mClassFile;

实践

MANIFEST.MF配置

在pom文件中添加下面的代码,根据需要修改参数值

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-jar-plugin</artifactId>
            <configuration>
                <archive>
                    <addMavenDescriptor>false</addMavenDescriptor>
                    <manifest>
                        <addClasspath>true</addClasspath>
                    </manifest>
                    <manifestEntries>
                        <Premain-Class>
                            com.qisi.agent.InterviewByteButtyAgent
                        </Premain-Class>
                        <Agent-Class>
                            com.qisi.agent.InterviewByteButtyAgent
                        </Agent-Class>
                        <Can-Redefine-Classes>
                            true
                        </Can-Redefine-Classes>
                        <Can-Retransform-Classes>
                            true
                        </Can-Retransform-Classes>
                        <Built-By>
                            qisi
                        </Built-By>
                    </manifestEntries>
                </archive>
            </configuration>
        </plugin>
    </plugins>
</build>

-javaagent:

在这种方式下,起作用的是permain,也就是说-javaagent和permain方法是配套使用的。
核心就是添加一个自定义的ClassFileTransformer,可以另起一个类,也可以这样匿名类。
如果只是熟悉流程可以像下面一样,直接打印一些日志,不去修改类;

    public static void premain(String agentArgs, Instrumentation instrumentation) {
        System.out.println("enhance by premain,params:"+agentArgs);
        instrumentation.addTransformer(new ClassFileTransformer() {
            @Override
            public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
                                    ProtectionDomain protectionDomain, byte[] classfileBuffer)
                    throws IllegalClassFormatException {
                System.out.println("premain load Class     :" + className);
                return classfileBuffer;
            }
        }, true);
    }

如果要真实修改,需要引入asm javassist bytebuddy等修改字节码的框架。下面这部分就是使用了bytebuddy,作用是让任何类的testAgent方法,都返回固定值transformed

public static void premain(String agentArgs, Instrumentation instrumentation) throws ClassNotFoundException {
    System.out.println("enhance by permain InterviewByteButtyAgent,params:"+agentArgs);
    new AgentBuilder.Default().type(any()).transform(new AgentBuilder.Transformer() {
        @Override
        public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader, JavaModule module) {
            return builder.method(named("testAgent"))
                    .intercept(FixedValue.value("transformed"));
        }
    }).installOn(instrumentation);
}

编写完之后,就可以在任意项目添加一个存在testAgent方法的进行尝试了,比如
java -javaagent:/xxxx/path/agent-1.0-SNAPSHOT.jar=key1:value1,key2:value2 -jar AppDemo.jar

attach

agentmain

这种方式需要实现agentmain方法,和permian不太一样的地方是需要在addTransformer之后触发需要retransformClasses想要加强的类。

public static void agentmain(String agentArgs, Instrumentation instrumentation) {
    System.out.println("enhance by agentmain,params:"+agentArgs);
    instrumentation.addTransformer(new ClassFileTransformer() {
        @Override
        public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
                                ProtectionDomain protectionDomain, byte[] classfileBuffer)
                throws IllegalClassFormatException {
            System.out.println("agentmain load Class     :" + className);
            return classfileBuffer;
        }
    }, true);
    try {
        instrumentation.retransformClasses(Class.forName("com.qisi.mybatis.app.controller.FirstRequestController"));
    } catch (UnmodifiableClassException e) {
        e.printStackTrace();
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
}

同样,提供一个bytebuddy的例子,下面这个则是指定修改FirstRequestController的testAgent方法的返回值为transformed

public static void agentmain(String agentArgs, Instrumentation instrumentation) throws ClassNotFoundException {
    System.out.println("enhance by agentmain InterviewByteButtyAgent,params:"+agentArgs);
    //这里RedefinitionStrategy必须注意,默认的DISABLED是不支持retransform
    new AgentBuilder.Default().with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION).type(new AgentBuilder.RawMatcher() {
        @Override
        public boolean matches(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, Class<?> classBeingRedefined, ProtectionDomain protectionDomain) {
            return typeDescription.getName().contains("FirstRequestController");
        }
    }).transform(new AgentBuilder.Transformer() {
        @Override
        public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader, JavaModule module) {
            System.out.println("enhance"+typeDescription.getName());
            return builder.method(named("testAgent"))
                    .intercept(FixedValue.value("transformed"));
        }
        //这里采用disableClassFormatChanges的方案,好像还可以使用advice
    }).disableClassFormatChanges().installOn(instrumentation);
    try {
        instrumentation.retransformClasses(Class.forName("com.qisi.mybatis.app.controller.FirstRequestController"));
    } catch (UnmodifiableClassException e) {
        e.printStackTrace();
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
}

VirtualMachine

不同于-javaagent命令,这里需要使用自jdk6开始提供的VirtualMachine类,在tool.jar包里
下面的方法是我参考arthas写的一个attach的流程,选择我们想要attach的进程,然后加载我们上面写好的jar包就好了。

public class AgentTest {
    public static void main(String[] args) throws IOException, AttachNotSupportedException {
        String pid = null;
        try {
            Process jps = Runtime.getRuntime().exec("jps");
            InputStream inputStream = jps.getInputStream();
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
            String line;
            while ((line = bufferedReader.readLine()) != null) {
                System.out.println(line);
            }
            System.out.println("选择要attach的进程");
            pid= new Scanner(System.in).nextLine();
            System.out.println("选择的pid是"+pid);
        } catch (IOException e) {
            e.printStackTrace();
        }
        for (VirtualMachineDescriptor virtualMachineDescriptor : VirtualMachine.list()) {
            if (virtualMachineDescriptor.id().equals(pid)){
                VirtualMachine attach = VirtualMachine.attach(virtualMachineDescriptor);
                try {
                    attach.loadAgent("/xxxxx/agent/target/agent-1.0-SNAPSHOT.jar","参数1,参数2");
                } catch (AgentLoadException e) {
                    e.printStackTrace();
                } catch (AgentInitializationException e) {
                    e.printStackTrace();
                } finally {
                    attach.detach();
                }
                break;
            }
        }
    }
}

参考文档:

探秘 Java 热部署二(Java agent premain)
JAVA热更新1:Agent方式热更 | 花隐间-JAVA游戏技术解决方案
ByteBuddy入门教程文章来源地址https://www.toymoban.com/news/detail-704784.html

到了这里,关于Java Agent技术的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 公司创建百度百科需要哪些内容?

    一个公司或是一个品牌想要让自己更有身份,更有知名度,更有含金量,百度百科词条是必不可少的。通过百度百科展示公司的详细信息,有助于增强用户对公司的信任感,提高企业形象。通过百度百科展示公司的发展历程、领导团队、企业荣誉等,可以彰显公司的实力和行

    2024年02月03日
    浏览(39)
  • Java之包装类的算法小题的练习

    需求: 键盘录入一些1~10日之间的整数,并添加到集合中。直到集合中所有数据和超过200为止。 代码示例: 需求: 自己实现parseInt方法的效果,将字符串形式的数据转成整数。要求:字符串中只能是数字不能有其他字符最少一位,最多10位 0不能开头 代码示例: 需求: 定义一

    2024年02月09日
    浏览(44)
  • 什么是分库分表?为什么需要分表?什么时候分库分表

    不急于上手实战  ShardingSphere  框架,先来复习下分库分表的基础概念,技术名词大多晦涩难懂,不要死记硬背理解最重要,当你捅破那层窗户纸,发现其实它也就那么回事。 分库分表是在海量数据下,由于单库、表数据量过大,导致数据库性能持续下降的问题,演变出的技

    2023年04月26日
    浏览(146)
  • 【JavaEE基础学习打卡02】是时候了解JavaEE了

    📜 本系列教程适用于 Java Web 初学者、爱好者,小白白。我们的天赋并不高,可贵在努力,坚持不放弃。坚信量最终引发质变,厚积薄发。 🚀 文中白话居多,尽量以小白视角呈现,帮助大家快速入门。 🎅 我是 蜗牛老师 ,之前网名是 Ongoing蜗牛 ,人如其名,干啥都慢,所

    2024年02月13日
    浏览(39)
  • UWB高精度人员定位系统源码,微服务+java+ spring boot+ vue+ mysql技术开发

    工业物联网感知预警体系,大中小企业工业数字化转型需求的工业互联网平台 工厂人员定位系统是指能够对工厂中的人员、车辆、设备等进行定位,实现对人员和车辆的实时监控与调度的系统,是智慧工厂建设中必不可少的一环。由于工厂的工作环境比较复杂,如果管理不当

    2024年02月11日
    浏览(58)
  • 不需要本地部署大模型,modelscope-agent加qwen-max免费搭建自己的定制机器人

    最近阿里开源了通用大模型qwen-72b,正在为怎么本地化部署发愁,转眼看到提供了qwen-max相关接口的免费试用(据说就是基于qwen-72b大模型),这就来体验一番。 开通阿里云灵积平台,并创建api-key python:3.10+; pydantic 2.0以上,老版本pydantic会报一个tool_schema.model_dump_json的函数错误

    2024年01月25日
    浏览(59)
  • 关于mysql8.0及以上版本连接navicat时候报错(密码加密方式需要修改)

    该错误的原因是在MySQL8之前版本中加密规则是mysql_native_password,而在MySQL8以后的加密规则为caching_sha2_password。 解决此问题有两种方法,一种是更新navicat驱动来解决此问题,一种是将mysql用户登录的加密规则修改为mysql_native_password。此处采用第二种方式。 具体做法: 1.使用c

    2024年02月11日
    浏览(50)
  • Gitee ---- 在clone的时候需要用户密码 -- Incorrect username or password (access token)

    在gitee 使用 git clone 指令进行克隆的时候会出现一个弹出框 第一种 注意这里用户名是输入 邮箱地址、邮箱地址、邮箱地址 密码还是自己的登陆(gitee)的密码 问题: 凭证用户名和密码还是以前的,导致无法自动登陆(ps: 成功登陆过一次会自动生成凭证) 文字版: win + R 打开命令

    2024年02月05日
    浏览(47)
  • AI Agent新对决:LangGraph与AutoGen的技术角力

    AI Agent变革未来,LangGraph对抗AutoGen ©作者| Blaze 来源| 神州问学 引言 比尔.盖茨曾在他的博客上发表一篇文章:《AI is about to completely change how you use computers》。在文章中,比尔·盖茨探讨 AI Agent 对我们未来生活的巨大影响,他谈到AI Agent对 教育、医疗保健、娱乐、生产力 等领

    2024年04月27日
    浏览(33)
  • 如何确认linux的包管理器是yum还是apt,确认之后安装其他程序的时候就需要注意安装命令

    打开终端 输入apt,下图中提示未找到命令,则基本上包管理工具就是用yum的  输入yum,我们看到有打印信息,则说明包管理工具是yum的,离线安装命令使用rpm

    2024年02月09日
    浏览(43)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包