Agent机制-整理

agent有两种: native(jvmti接口) 和 java层面的(instrumentation)

  • c/c++ 层面的 jvmti 接口

    • jvmti官方文档
    • JVM TI是JDK提供的一套用于开发JVM监控, 问题定位与性能调优工具的通用编程接口(API)。 通过JVMTI,我们可以开发各式各样的JVMTI Agent。这个Agent的表现形式是一个以c/c++语言编写的动态共享库
    • JVMTI Agent原理
      • Java启动或运行时,动态加载一个外部基于JVM TI编写的dynamic module到Java进程内,然后触发JVM源生线程Attach Listener来执行这个dynamic module的回调函数。在函数体内,你可以获取各种各样的VM级信息,注册感兴趣的VM事件,甚至控制VM的行为。
    • jvmti api
      • JVMTI是基于事件驱动的,JVM每执行到一定的逻辑就会调用一些事件的回调接口(如果有的话),这些接口可以供开发者去扩展自己的逻辑。
      • 可以获取各种各样的信息
    • 开发jvm ti agent,简单的来讲,就是开发一个c/c++的共享库。在windows下后缀是dll,linux/unix下是so,mac下就是dylib。所以我们创建工程和编译环境的时候,记得以共享库(share library)的形式来构建
    • 两种方式载入
      • 随java进程启动时,自动载入共享库
        • 共享库路径是环境变量路径: java -agentlib:foo=opt1,opt2,java启动时会从linux的LD_LIBRARY_PATH或windows的PATH环境变量定义的路径处装载foo.so或foo.dll,找不到则抛异常
        • 以绝对路径的方式装载共享库: java -agentpath:/home/admin/agentlib/foo.so=opt1,opt2 Sample
      • java运行时,通过attach api动态载入
        public static void main(String[] args) throws Exception { // args[0]为java进程id VirtualMachine virtualMachine = com.sun.tools.attach.VirtualMachine.attach(args[0]); // args[1]为共享库路径,args[2]为传递给agent的参数 virtualMachine.loadAgentPath(args[1], args[2]); virtualMachine.detach(); }
    • 开发jvmti agent
      • jvmti.h头文件里包含了所有jvm ti要用到的数据结构和回调函数定义
      • 确定JVMTI的启动方式
        • JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM vm, char options, void *reserved)//启动载入方式
        • JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM jvm, char options, void *reserved)//动态载入方式
        • JNIEXPORT void JNICALL Agent_OnUnload(JavaVM *vm)//卸载都是一样
      • 具体例子可以google或jvmti官网上找
  • java层面的instrumentation

    • Instrumentation是Java5提供的新特性,使用Instrumentation,开发者可以构建一个代理,用来监测运行在JVM上的程序

    • java.lang.instrument.ClassFileTransformer

      • 每个代理类必须实现 ClassFileTransformer接口,这个接口提供了一个transform方法:
      • byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException
      • 通过这个方法,代理可以得到虚拟机载入的类的字节码,并可对其进行修改,完成字节码级的修改。
      • classfileBuffer这个便是被代理类字节码流,正是通过操作这个buffer完成对字节码的修改
      • 对于函数的返回值,如果返回null,则表示不对类的字节码做任何的修改,否则应该返回修改过的byte[]对象
    • 提供一个公共的静态方法 public static void premain(String agentArgs, Instrumentation inst)

      • 一般会在这个方法中创建一个代理对象,通过Instrumentation对象的addTransformer()方法,将创建的代理对象再传递给虚拟机
        public class HelloWorld implements ClassFileTransformer { @Override public byte[] transform(ClassLoader loader, String className, Class<>; classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { System.out.println("java.lang.instrument, hello world!"); return null; } public static void premain(String args,Instrumentation inst){ inst.addTransformer(new HelloWorld()); } }

      public class Example { public static void main(String[] args){ System.out.println("main class of proxy!"); } }

      • 将agent类HelloWorld编译成可运行的jar(helloworld.jar),这里注意在manifest文件中添加premain入口,也即其配置为:
        • Premain-Class: cn.dstrace.instrument.HelloWorld
      • 运行
        • java -javaagent:helloworld.jar cn.dstrace.instrument.Example
    • 小节

      • java的各种性能监控工具中都有instrument的身影,如jconsole等
      • 这样的特性实际上提供了一种虚拟机级别支持的 AOP 实现方式
    • 动态的javaagent

      • 在 Java SE6 里面,最大的改变使运行时的 Instrumentation 成为可能;java attach api
      • 但是在实际的很多的情况下,我们没有办法在虚拟机启动之时就为其设定代理
      • jdk5局限
        • 在 Java SE 5 当中,开发者可以让 Instrumentation 代理在 main 函数运行前执行。
        • 在 Java SE 5 当中,开发者只能在 premain 当中施展想象力,所作的 Instrumentation 也仅限与 main 函数执行前,这样的方式存在一定的局限性。
        • 在 Java SE 5 的基础上,Java SE 6 针对这种状况做出了改进,开发者可以在main 函数开始执行以后,再启动自己的 Instrumentation 程序。
        • 在 Java SE 6 的 Instrumentation 当中,有一个跟 premain“并驾齐驱”的“agentmain”方法,可以在 main 函数开始运行之后再运行。跟 premain 函数一样, 开发者可以编写一个含有“agentmain”函数的 Java 类:
        • 在 Java SE 6 的新特性里面,有一个不太起眼的地方,揭示了 agentmain 的用法。这就是Java SE 6 当中提供的 Attach API
  • javaagent原理完全解读

    • javaagent的主要的功能如下
      • 可以在加载class文件之前做拦截把字节码做修改
      • 可以在运行期将已经加载的类的字节码做变更,但是这种情况下会有很多的限制
      • 还有其他的一些小众的功能
        • 获取所有已经被加载过的类
        • 获取所有已经被初始化过了的类(执行过了clinit方法,是上面的一个子集)
        • 获取某个对象的大小
        • 将某个jar加入到bootstrapclasspath里作为高优先级被bootstrapClassloader加载
        • 将某个jar加入到classpath里供AppClassloard去加载
        • 设置某些native方法的前缀,主要在查找native方法的时候做规则匹配
    • 其实我们每天都在和JVMTIAgent打交道,只是你可能没有意识到而已,比如我们经常使用eclipse等工具对java代码做调试,其实就利用了jre自带的jdwp agent来实现的,只是由于eclipse等工具在没让你察觉的情况下将相关参数(类似-agentlib:jdwp=transport=dt_socket,suspend=y,address=localhost:61349)给自动加到程序启动参数列表里了,其中agentlib参数就是用来跟要加载的agent的名字,比如这里的jdwp(不过这不是动态库的名字,而JVM是会做一些名称上的扩展,比如在linux下会去找libjdwp.so的动态库进行加载,也就是在名字的基础上加前缀lib,再加后缀.so),接下来会跟一堆相关的参数,会将这些参数传给Agent_OnLoad或者Agent_OnAttach函数里对应的options参数。
    • javaagent
      • 说到javaagent必须要讲的是一个叫做instrument的JVMTIAgent(linux下对应的动态库是libinstrument.so),因为就是它来实现javaagent的功能的,另外instrument agent还有个别名叫JPLISAgent(Java Programming Language Instrumentation Services Agent),从这名字里也完全体现了其最本质的功能:就是专门为java语言编写的插桩服务提供支持的。
    • instrument agent (libinstrument.so实现)
      • instrument agent实现了Agent_OnLoad和Agent_OnAttach两方法
      • instrument agent的核心数据结构如下
        • tobecontinued...
  • References

    • 笨神-JVM源码分析之javaagent原理完全解读

你可能感兴趣的:(Agent机制-整理)