初探Java agent技术

摘要:
前言不知道各位小伙伴在此之前,是否有听过或者了解过agent相关技术,没有听说过也没有关系,我们今天的目的就是介绍agent的相关技术,探讨agent的应用场景,分享一些实际开发中的应用案例。AgentAgent是什么Agent中文含义代理,但是在java中我跟喜欢称它为探针而非代理,尽管他也属于代理技术,但是代理本身并不能体现agent的作用。agent技术是在JDK1.5引入的,通过agent技术,我们可以构建一个独立于应用程序的代理程序,用来协助监测、运行甚至替换其他JVM上的程序。

前言

不知道各位小伙伴在此之前,是否有听过或者了解过agent相关技术,没有听说过也没有关系,我们今天的目的就是介绍agent的相关技术,探讨agent的应用场景,分享一些实际开发中的应用案例。

印象中,我第一次了解agent技术,是在分享skywalking这款工具的时候,skywalking与我们项目的绑定就是通过agent来实现的。好了,先说这么多,接下来我们就来详细介绍下agent的一些技术点。

Agent

Agent是什么

Agent中文含义代理,但是在java中我跟喜欢称它为探针而非代理,尽管他也属于代理技术,但是代理本身并不能体现agent的作用。

agent技术是在JDK1.5引入的,通过agent技术,我们可以构建一个独立于应用程序的代理程序,用来协助监测、运行甚至替换其他JVM上的程序。使用它可以实现虚拟机级别的AOP功能。

Agent分为两种,一种是在主程序之前运行的Agent,一种是在主程序之后运行的Agent(前者的升级版,1.6以后提供),稍后我们会有具体实例展示。

Agent能干什么

首先它最大的作用就是解耦,比如skywalking中的应用,我们不需要对我们的程序做任何修改,只需要通过agent技术引入skywalking的代理包即可;其次最常应用的场景就是jvm级的AOP,比如jvm的监测;另一种就是类似热部署这样的字节码动态操作。

Agent技术演示

说了这么多好多小伙伴肯定看的云里雾里的,接下来我们通过两个简单示例,来演示下Agent技术的神奇之处。

先看第一种,也就是在主程序之前运行的Agent

在主程序之前运行的Agent

首先我们创建一个maven项目,编写这样一个Agent类:

import java.lang.instrument.Instrumentation;
/**
 * 在主程序之前运行的Agent
 */
public class PremainAgent {
    public static void premain(String preArgs, Instrumentation instrumentation) {
        System.out.println("premainAgent.premain start");
        System.out.println("preArgs: " + preArgs);
        Class[] allLoadedClasses = instrumentation.getAllLoadedClasses();
        for (Class allLoadedClass : allLoadedClasses) {
            System.out.println("premainAgent LoadedClass: " + allLoadedClass.getName());
        }
    }
}

这里的方法名和参数列表是固定的,根据方法名我们能看出这个方法应该是运行在main方法之前的,等下测试下就知道了。

接着,我们在pom.xml文件中增加如下内容:

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-jar-plugin</artifactId>
            <version>2.4</version>
            <configuration>
                <archive>
                    <manifest>
                        <addClasspath>true</addClasspath>
                    </manifest>
                    <manifestEntries>
                        <Premain-Class>io.github.syske.agent.PremainAgent</Premain-Class>
                    </manifestEntries>
                </archive>
            </configuration>
        </plugin>
    </plugins>
</build>

上面这些内容是配置我们构建时生成的MANIFEST文件,通常我们打的jar包都有这个文件。最核心的配置就是Premain-Class,这里配置的是我们探针的类名,如果没有这个配置,我们的premain方法是不会被识别的。

初探Java agent技术第1张

然后我们通过maven把我们当前项目打成一个jar包,打完包之后的jar文件如上图,打开MANIFEST.MF文件,你会发现我们指定的Premain-Class也被写入了,这时候我们的包就是打好了,下面就是运行测试了。

初探Java agent技术第2张

运行也很简单,只需要找到一个可运行的jar包,比如一个springboot项目的包,然后在jar文件的启动命令中,增加如下配置即可:

--javaagent:你的agent文件完整路径/agent文件名.jar
# 例如我的:D:workspacelearningexample-everydayexample-2021.07.02	argetexample-2021.07.02-1.0-SNAPSHOT.jar

这里我用之前的一个springboot项目演示:

java -javaagent:D:workspacelearningexample-everydayexample-2021.07.02	argetexample-2021.07.02-1.0-SNAPSHOT.jar -jar 

大家注意,在javaagentagent文件之间不能有空格,否则会报如下错误

初探Java agent技术第3张

如果你的配置和启动命令都没有问题,在启动控制台应该会显示如下信息:

初探Java agent技术第4张

我们可以看到premain方法在我们springboot项目启动前被执行了,但是preArgsnull,这是由于我们没有注入参数,所以显示为空,我们可以通过这样的方式为preArgs注入参数:

java -javaagent:D:workspacelearningexample-everydayexample-2021.07.02	argetexample-2021.07.02-1.0-SNAPSHOT.jar=syske -jar  .springboot-learning-0.0.1-SNAPSHOT.jar

也就是在我们的agent包后面直接=需要注入的参数值就可以了,然后再次执行你会发现参数已经被注入了:

初探Java agent技术第5张

关于Instrumentation这个参数,今天先不讲了,我们说的字节码操都是基于这个参数进行操作的。下面我们看下第二种Agent

在主程序之后运行的Agent

相比第一种agent,第二种是在main方法启动后运行agent方法,而且这种方式应用最广泛,比如我们前面说的热部署,就是这种方式实现的,下来我们看下具体如何实现。

第一步,也是写Agent实现类:

public class AgentMain {
    public static void agentmain(String args, Instrumentation instrumentation) {
        System.out.println("AgentMainTest.agentmain start");
        System.out.println("args: " + args);
        Class[] allLoadedClasses = instrumentation.getAllLoadedClasses();
//        for (Class allLoadedClass : allLoadedClasses) {
        System.out.println("AgentMainTest LoadedClass: " + allLoadedClasses[0].getName());
//        }
    }
}

和第一种agent不一样的只有方法名,连参数都一幕一样,这里为了方便查看,我只打印了一行数据。然后我们还需要修改下maven的打包配置,需要把之前的Premain-Class标签改成Agent-Class,其他都一样:

初探Java agent技术第6张

然后再打包,但是这一次运行方式和第一次不一样,这里的agent要通过代码来启动。

我们创建一个测试类,写一个main方法,因为要用到tools包下的类,所以要先引入tools包:

初探Java agent技术第7张

测试类如下:

import com.sun.tools.attach.AgentInitializationException;
import com.sun.tools.attach.AgentLoadException;
import com.sun.tools.attach.AttachNotSupportedException;
import com.sun.tools.attach.VirtualMachine;
import com.sun.tools.attach.VirtualMachineDescriptor;

public class MainTest {
    public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException {
        List<VirtualMachineDescriptor> machineDescriptorList = VirtualMachine.list();
        for (VirtualMachineDescriptor virtualMachineDescriptor : machineDescriptorList) {
            if ("io.github.syske.agent.MainTest".equals(virtualMachineDescriptor.displayName())) {
                String id = virtualMachineDescriptor.id();
                VirtualMachine virtualMachine = VirtualMachine.attach(id);
                virtualMachine.loadAgent("D:\workspace\learning\example-everyday\example-2021.07.02\target\example-2021.07.02-1.0-SNAPSHOT.jar",
                        "syske agentmain");
                virtualMachine.detach();
            }
        }
        System.out.println("MainTest start");
    }
}

这里解释下,VirtualMachine.list()是获取当前运行的所有jvm虚拟机,运行结果如下:

初探Java agent技术第8张

其中的VirtualMachineDescriptor包含如下信息:

初探Java agent技术第9张

我们需要从中拿出displayNameio.github.syske.agent.MainTest,也就是当前类的虚拟机描述信息,然后根据虚拟机id拿到对应虚拟机,然后为该虚拟机加载Agent包,同时我们还在加在Agent包的同时,传入了syske agentmain参数,这里的参数和我们第一种方式=的方式类似,就相当于给args赋值,然后断开虚拟机连接。

运行代码,结果如下:

初探Java agent技术第10张

根据运行结果,我们发现这种Agent并发是在main方法之后执行,而是可以在你任意需要的地方调用。相比于第一种,确实要灵活一些。

总结

Agent其实在日常开发中经常用到,但是由于我们大部分情况下都用的是继承开发环境,所以感知不强,像日志采集、热部署等基本上都是基于Agent来实现的。

当然,agent最大的好处在于,它可以有效解耦,实现jvm层面的AOP,而且它又支持字节码操作,如果你玩的够溜,你就可以实现更多强大功能,而且可玩性还高,简直可以为所欲为。

今天我们暂时就讲这么多,后面抽时间用agent实现一些具体的功能,比如字节码操作,让大家真正见识Agent的强大之处。

免责声明:文章转载自《初探Java agent技术》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇Debug与Release版本的区别用MATLAB画立体桃心/心形下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

随便看看

FastDFS安装

FastDFS安装包FastDFS安装包百度网盘密码aj4f下载后把安装包移动到服务器里面这里我把安装包放在opt/FastDFSFastDFS安装安装环境在本地安装就需要安装gcc环境yum-yinstallcmakemakegcc-c++在阿里服务器因为帮你配置好了的解压libfastcommon到指定目录解压-C指定解压的目录#解压[root@rzkF...

background:url 的使用方法

1#pingfenli{227px;3float:left;4height:28px;5cursor:pointer;6background:urlno-repeat00;7list-style:none;8}background:url的使用方法,后面的两个数字代表的是图片在屏幕上显示的位置。...

Ubuntu 下查看CPU 信息命令

看看带有“处理器”一词的行数,即逻辑CPU的数量。因此,您可以在cmd下输入以下命令:cat/proc/cpuinfo|greproprocessor|wc-l因此,C++程序自然会想到使用strstr函数来查找processor关键字的出现次数。...

PLSQL操作Oracle创建用户和表(含创建用户名和密码)

1》 打开PLSQL,填写用户名和密码,为数据库选择ORCL2,成功登录后可以在界面顶部看到以下信息system@ORCL这意味着用户系统处于登录状态。菜单栏中的会话可以登录和注销。...

微信小程序生成带参数的二维码(小程序码)独家asp.net的服务端c#完整代码

1) 我第一次使用wx。小程序端请求调用API,发现这是一个坑!@-_~Page:'pages/index/index',//在此处填写要跳转到的小程序页面。你不能在它前面添加/oh。发布后必须为1024页//小程序代码的边长,以像素为单位,范围[2801280]},标头:{'content-type':“application/json;charset=U...

iReport制作报表,字数过多换行问题

1.当字段中显示的数据太长而无法放入表中时,需要自动换行。选择要更改的表(显示动态内容的字段),并将Stretchwithoverflow属性设置为选中。未选中前:选中后:2.然而,桌子坏了,非常难看。此时,我们需要设置一个属性,使同一行中的其他字段保持与换行字段相同的高度。此时,我们需要框选要显示在整行中的动态字段和表;将属性StretchType设置为R...