当前位置: 首页 > 后端技术 > Java

JavaAgent技术及原理

时间:2023-04-02 09:36:19 Java

InstrumentationInstrumentation是JDK1.5的新特性。通过java.lang.instrument包,可以实现一个独立于应用程序的Agent程序,可以替换和修改类定义。通过这样的功能,开发者可以实现灵活的运行时虚拟机监控和Java类增强,实际上提供了虚拟机级别的AOP实现。下面的JavaAgentDemo介绍了通过java.lang.instrument编写Agent的一般方法。实现Agent启动方法JavaAgent支持在目标JVM启动时加载,也支持在目标JVM运行时加载。这两种不同的加载方式使用不同的入口函数。如果需要在目标JVM启动时加载Agent,可以选择Implementthefollowingmethod:[1]publicstaticvoidpremain(StringagentArgs,Instrumentationinst);[2]publicstaticvoidpremain(StringagentArgs);JVM会先搜索[1],如果找不到[1],再搜索[2]。如果要在目标JVM运行时加载Agent,需要实现如下方法:[1]publicstaticvoidagentmain(StringagentArgs,Instrumentationinst);[2]publicstaticvoidagentmain(StringagentArgs);前两组方法参数agentArgs是随--javaagent一起传递的程序参数。如果这个字符串表示多个参数,则需要自己解析这些参数。Instrumentation是Instrumentation类型的对象,由JVM自动传入。我们可以使用此参数进行类增强和其他操作。publicclassAgent{publicstaticvoidpremain(StringagentArgs,Instrumentationinstrumentation){System.out.println("Agentpremainstart...");System.out.println("代理参数:"+agentArgs);instrumentation.addTransformer(newAppInitTramsformer());}publicstaticvoidagentmain(StringagentArgs,Instrumentationinstrumentation){System.out.println("Agentagentmainstart...");System.out.println("代理参数:"+agentArgs);instrumentation.addTransformer(newAppInitTramsformer());}}实现Transformer在Instrumentation接口中,通过addTransformer方法添加一个类transformer,类transformer由ClassFileTransformer接口实现。ClassFileTransformer接口中唯一的方法transform用于实现类转换。当加载类时,会调用transform方法进行类转换。在运行时,我们可以通过Instrumentation的redefineClasses方法重新定义类。实现时注意不要增删改名字段和方法,改变方法的签名或类的继承关系。可以使用ASM、javassist、bytebuddy等工具修改字节码。在以下示例中,使用了javassist。公共类AppInitTramsformer实现ClassFileTransformer{privatestaticfinalStringINJECTED_CLASS="com.github.godshang.agent.Agent";@Overridepublicbyte[]transform(ClassLoaderloader,StringclassName,ClassclassBeingRedefined,ProtectionDomainprotectionDomain,byte[]classfileBuffer)抛出IllegalClassFormatException{StringrealClassName=className.replace("/",".");if(realClassName.equals(INJECTED_CLASS)){System.out.println("摘抄到的类名:"+realClassName);CtClassctClass;尝试{ClassPoolclassPool=ClassPool.getDefault();ctClass=classPool.get(realClassName);CtMethod[]declaredMethods=ctClass.getDeclaredMethods();for(CtMethodmethod:declaredMethods){System.out.println(method.getName()+"方法被截取");方法d.addLocalVariable("时间",CtClass.longType);method.insertBefore("System.out.println(\"---开始执行---\");");method.insertBefore("time=System.currentTimeMillis();");method.insertAfter("System.out.println(\"---结束执行---\");");method.insertAfter("System.out.println(\"运行消耗时间:\"+(System.currentTimeMillis()-time));");}返回ctClass.toBytecode();}catch(Throwablee){System.out.println(e.getMessage());e.printStackTrace();}}返回新字节[0];}}写在MANIFEST.MF文件中的Agent怎么才能被外部应用程序知道呢?依赖MANIFEST.MF文件文件具体路径为:src/main/resources/META-INF/MANIFEST.MF。Manifest-Version:1.0Premain-Class:com.github.godshang.agent.AgentAgent-Class:com.github.godshang.agent.AgentCan-Redefine-Classes:trueCan-Retransform-Classes:trueMANIFEST.MF文件Premain-Class对应premain入口是在启动时加载Agent,Agent-Class对应agentmain入口,是在运行时加载Agent。Can-Redefine-Classes表示是否重新定义这个代理需要的类,默认值为false。Can-Retransform-Classes表示是否可以重新转换此代理所需的类,默认值为false。除了直接创建MANIFEST.MF文件外,还可以在Maven中配置编译打包插件。org.apache.maven.pluginsmaven-jar-plugin3.2.0true1.0com.github。godshang.agent.Agentcom.github.godshang.agent.AgenttruetrueLoadAgent如果是启动时静态加载,需要在启动参数中加上-javaagent参数:publicclassAppMain{/***-javaagent:E:\github\java-agent\agent\target\agent-1.0-SNAPSHOT.jar=hello*@paramargs*/publicstaticvoidmain(String[]args){AppInitappInit=newAppInit();应用程序初始化();}}如果是运行时动态加载,可以参考如下代码:(vmd.displayName());if(vmd.displayName().endsWith("AttachMain")){尝试{VirtualMachinevm=VirtualMachine.attach(vmd.id());vm.loadAgent("E:\\github\\java-agent\\agent\\target\\agent-1.0-SNAPSHOT.jar=hello");vm.分离();}catch(Exceptione){e.printStackTrace();}}}AppInitappInit=newAppInit();应用程序初始化();}}JavaAgent原理Instrumentation的底层实现依赖于JVMTI,即JVMToolInterfaceJVMTIJVMTI(JVMToolInterface)是JavaVirtualComputer提供的Native编程接口,是JVM暴露给用户扩展的接口集合。运行时JVM的很多信息,比如线程,GC,都可以通过JVMTI外部进程获取。JVMTI是事件驱动的。JVM每次执行某个逻辑,都会触发一些事件回调接口。通过这些回调接口,用户可以扩展实现自己的逻辑。JVMTIAgent实现了JVMTI客户端程序,称为agent,其实就是利用JVMTI暴露的接口来实现用户自己的逻辑。JVMTIAgent中主要有3个方法:Agent_OnLoad方法,如果启动时加载了agent,就执行这个方法发送加载命令加载代理,在加载过程中调用Agent_OnAttach函数Agent_OnUnload方法,在卸载代理时调用Agent_OnUnload方法。如何使用C++开发Agent,可以参考这篇文章。InstrumentJVMTI是一组Native接口。在JavaSE5之前,Agent只能通过编写Native代码来实现。从JavaSE5开始,可以使用Java的Instrumentation接口编写代理。无论Agent是通过Native方式编写的,还是通过JavaInstrumentation接口编写的,它们的工作都是借助JVMTI完成的。JPLISAgent全称是JavaProgrammingLanguageInstrumentationServicesAgent,是一种特殊的JVMTIAgent,负责初始化所有通过Instrumentation接口编写的Agent,同时也承担了通过JVMTI实现Instrumentation中暴露的API的责任。参考https://docs.oracle.com/javas...https://cloud.tencent.com/dev...https://xz.aliyun.com/t/10186https://juejin.cn/post/708602...https://jueee.github.io/2020/...https://www.overops.com/blog/...

最新推荐
猜你喜欢