Java agent

JVMTI 及 Instrument

JVM Tool Interface(JVM TI)是一个作为工具作用的原生(native)接口。它提供了一种方式去检查 JVM 程序的状态或者控制它们的运行。用途包括但不限于:指标分析、调试、监控、线程分析、覆盖率分析工具。

JVM TI 在 JDK 5.0 引用。它替换了 JVMPI(JVM Profiler Interface) 和 JVMDI(JVM Debug Interface),这两者在 JDK6 不再提供。

在《深入理解Java虚拟机》第三版的 4.3.3 节对于 VisualVM 的介绍中,介绍了它的一个插件 BTrace。

Instrument是JVMTI(Java Virtual Machine Tool Interface,JVMTI)中的主要组成部分, 它提供了一套 agent(代理)机制。HotSpot虚拟机允许在不停止运行的情况下,更新已经加载的类的代码。

BTrace的作用是在不中断目标程序运行的前提下,通过HotSpot虚拟机的 Instrument 功能动态加入原本并不存在的调试代码。另外,阿里的 Arthas 也是基于此实现的

Java Agent

java.lang.instrument 包提供了可以通过 Java 语言编写 JVM 的 agent(Provides services that allow Java programming language agents to instrument programs running on the JVM.)。

以下是一个 agent 机制的示意图:

JVM architecture

通过 java agent,我们可以使用 Java 语言编写监测 JVM 程序的逻辑,以此可以实现监控、覆盖率分析、事件日志记录等需求,其原理就是对方法的字节码进行修改,这种方式对于原应用程序来说侵入性较低。

要通过 Java Agent 实现前面提到的功能需要两个步骤:

  1. 编写 Agent 逻辑
  2. 加载 Agent 到应用程序 JVM 中

编写 Agent

Java gents 机制的实现基础由 java.lang.instrument 包提供。这个包的结构较为简单且是自包含(不依赖其它)的,里面包含了两个异常类、一个 data 类、一个 class definition以及两个接口。当我们需要实现一个 Java agent 的时候需要用到两个类:ClassFileTransformerInstrumentation

image-20201224110021760

ClassFileTransformer

transform 方法

这一个接口提供了转换 class 文件的功能,这个转换动作会在 class 被 JVM define 之前进行。来看一下它唯一的一个方法:

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

这个方法就是被用来对字节码做转换,也就是我们要对 class 字节码做增强的地方。

参数
  1. 第一个参数:当前要 define class 的类加载器,如果是 null 则表示该类由 bootstrap 加载器加载的
  2. 第二个参数:当前要 define class 的类名,格式为 JVM 规范的内部格式,例如:“java/util/List”
  3. 第三个参数:如果当前调用是由类重定义(redefine)或者类重转换(retransform)触发的,传入的就是将要被重定义或者重转换的 Class 对象;如果当前调用仅仅是因为类加载而被触发,传入 null
  4. 第四个参数:the protection domain of the class being defined or redefined
  5. 第五个参数:该 class 文件的字节码格式的字节 buffer(不能修改这个数组)
返回值

是修改之后要返回的字节码文件的字节 buffer。

转换器类型

这里将 ClassFileTransformer 接口称为一个 class 转换器,一共有两种转换器:

  1. 可以重转换(retransformation capable)的转换器,在调用 Instrumentation.addTransformer 注册转换器的时候指定第二个参数 canRetransform 为 true。
  2. 不可重转换(retransformation incapable)的转换器,相反在注册转换器的时候 canRetransform 设置为 false。

转换器被触发的方式

当一个转换器通过 addTransformer 注册之后,其 transform 方法被调用的时机如下:

  1. 每一个新的 class 被定义(new class definition):由ClassLoader.defineClass 或者它对应的 native 方法触发。
  2. 每一个 class 被重定义(class redefinition):由 Instrumentation.redefineClasses 或者它对应的 native 方法触发。
  3. 每一个 class 被重转换(class retransformation)并且当前转换器是可重转换的:由 Instrumentation.retransformClasses 或者它对应的 native 方法触发。

transform 方法会在 class 字节码被 校验 或者 应用 之前被调用。

多转换器

如果存在多个转换器,这些转换器将会被组合成一条链条进行按序调用(前一个转换器输出的字节码数组作为下一个转换器的字节码输入)。下面是多转换器的执行顺序:

  • 不可重转换的转换器
  • 不可重转换的 native 转换器
  • 可以重转换的转换器
  • 可以重转换的 native 转换器

对于重转换(class retransformations)来说,不可重转换的转换器不会被调用,而是继续使用前一个转换器的输出作为下一个转换器的输入。

以上四组转换器,同组内的执行顺序和注册顺序一致。native 转换器可以通过 JVM TI 中的 ClassFileLoadHook 事件进行注册(Java Agent 是JVM TI Instrumentation 提供的其中一个 Java 钩子,通过 JNI 可以使用更多的钩子)。

输入第一个转换器的字节码数组

  • 对于新的 class 被定义:通过 ClassLoader.defineClass 传入的字节码
  • 对于 class 重定义:通过 Instrumentation.redefineClasses 传入的可变参数中的每一个元素的 ClassDefinition.getDefinitionClassFile() 的返回值
  • 对于 class 重转换:the bytes passed to the new class definition or, if redefined, the last redefinition, with all transformations made by retransformation incapable transformers reapplied automatically and unaltered(不是很理解,是指将不可重转换的转换器都执行了一遍,但是不会理会它的返回值?)

其它注意

  1. 如果转换器认为对于当前类无需做转换工作,应该返回 null;否则,应该创建一个新的字节数组,将 classfileBuffer 形参的内容拷贝到新数组中进行修改,然后返回该数组。classfileBuffer 本身不允许修改。也就是说 JVM 会根据转换器的返回结果是否为 null 而选择是使用其返回值还是传递给它的形参字节数组作为该 class 的新的字节码流。
  2. 在发生类的重转换或者重定义的场景中,转换器必须支持这样的一个语义:如果一个已经在进行类初始定义阶段被该转换器修改的类在后续再进行一个重转换或者重定义的时候,该转换器必须要确保第二次转换的 class 字节码相对于第一次转换的输出是有效、合理的。
  3. 如果当前转换器抛出了一个未捕获的异常,后续的转换器将会被继续调用,一切照常,类似于该转换器返回了 null。为了防止转换器中的一些未知行为抛出一些未检查异常,可以在转换器中捕获 Throwable 。如果转换器认为 classFileBuffer 不是一个有效格式的 class 字节码,它应该抛出 IllegalClassFormatException ,这会得到和返回 null 一样的效果,同时还起到一个对于非法格式字节码的记录(logging)和调试(debugging)的效果。

Instrumentation

这个 class 提供了修改 Java 程序代码(instrument Java programming language code)的服务,其中的注册服务允许我们注册自定义的 class 转换器 ClassFileTransformer

Instrumentation 指的是在方法中添加收集供工具使用的一些数据的额外的字节码。因为这些修改的侵入性很低,这些工具不会修改应用的状态或者行为。这些友好的工具包含:monitoring agents、profilers、覆盖率分析器(coverage analyzers)、事件记录器(event loggers)

monitoring 和 profiling 的概念,总的来说,前者应该是要包含后者的,即包含指标收集、分析、告警;后者则对于分析方面更专业

  • program profiling

    In software engineering, profiling (“program profiling”, “software profiling”) is a form of dynamic program analysis that measures, for example, the space (memory) or time complexity of a program, the usage of particular instructions, or the frequency and duration of function calls.

  • monitoring

    In computer science, event monitoring is the process of collecting, analyzing, and signaling event occurrences to subscribers such as operating system processes, active database rules as well as human operators.

这里有两种方式获取 Instrumentation 服务实例的方式:

  1. 当 JVM 在启动的时候指定了一个 agent class,它将会将一个 Instrumentation 实例传到 agent class 的 premain 方法(通过在 manifest 中添加一项 Premain-Class 指定该 agent class)
  2. 当 JVM 是启动之后才加载 agents 的时候,Instrumentation 实例将会被传递到 agent class 的 agentmain 方法(通过在 manifest 中添加一项 Agent-Class 指定该 agent class)

下面来看一下 Instrumentation 提供的服务(方法)。

1>注册转换器

void	addTransformer(ClassFileTransformer transformer, boolean canRetransform)  
void	addTransformer(ClassFileTransformer transformer) // same as addTransformer(transforrmer, false)
参数
  1. 要注册的 class 转换器
  2. 该转换器是否可重转换
描述

注册传入的 class 转换器,所有在后续发生的 class 定义对于当前注册的 class 转换器都是可见的,除了那些被任何已注册转换器依赖的 class 的定义。转换器被调用的时机和顺序参考上面的描述。同一个转换器可以被注册多次,但强烈建议不要这样做,而是创建一个新的转换器对象。

2>注销转换器

boolean removeTransformer(ClassFileTransformer transformer)
参数
  1. 要注销的转换器
返回值
  • 如果转换器可以找到并且删除了,返回 true
  • 如果无法找到该转换器,返回 false
描述

注销指定的转换器,之后再进行的 class 定义将不会应用该转换器。Removes the most-recently-added matching instance of the transformer. Due to the multi-threaded nature of class loading, it is possible for a transformer to receive calls after it has been removed. Transformers should be written defensively to expect this situation.(移除匹配到的最晚添加的实例,因为类加载的多线程特性,可能会出现一个转换器在被注销之后还被调用的情况。在编写转换器的时候应该要考虑、预防这种情况)。

3>查看当前 JVM 是否支持 class 重转换

boolean isRetransformClassesSupported()
描述

class 重转换是 JVM 的一个可选能力。当且仅当在 agent JAR 文件中的 manifest 中添加 Can-Retransform-Classes 属性为 true 的时候,JVM 才会支持重转换。在一个 JVM 实例中,对该方法的多次调用总是会返回相同的值。

4>重转换 classes

void retransformClasses(Class... classes)
                 throws UnmodifiableClassException
参数
  1. 需要被重转换的 classes 数组;设置一个长度为 0 的空数组,该方法则什么也不做
描述

对于提供的 classes 集合进行重转换。该方法使得已经被加载的 classes 可以被 instrument,无论这些 classes 是否已经被转换器处理过,都会再被转换一次,以下是它的执行步骤:

  1. 从 initial class file bytes 开始。

    initial class file bytes 指的是传递给 ClassLoader.defineClass 或者 redefineClasses 的字节数组(在任何转换器被调用之前)。不过它们(传递给方法之前和方法处理后)可能不是完全一模一样的。

    • 常量池的布局和内容可能不太一样
    • 常量池的条目可能变多或者变少
    • 常量池条目的顺序可能不太一样
    • 一些属性可能不存在了
    • 字节码内部元素的顺序没有被保证,例如方法之间的顺序,因为这些顺序是没有意义的

    但是在方法内的字节码中引用的常量池索引时候一致的。

  2. for each transformer that was added with canRetransform false, the bytes returned by transform during the last class load or redefine are reused as the output of the transformation; note that this is equivalent to reapplying the previous transformation, unaltered; except that transform is not called

  3. 对于可以重转换的转换器,它们的 transform 方法将会被调用。

  4. 转换后的 class 文件字节码将会被为该 class 的新的定义

The order of transformation is described in the transform method. This same order is used in the automatic reapplication of retransformation incapable transforms.

这个方法操作的是一个集合,允许一次性操作多个互相影响的 classes。如果被重转换的方法已经存在一些栈帧,这些栈帧的字节码将会保持和原方法一致不变,而重转换后的方法将会在后续的新的调用中被使用。

This method does not cause any initialization except that which would occur under the customary JVM semantics. In other words, redefining a class does not cause its initializers to be run. The values of static variables will remain as they were prior to the call.

Instances of the retransformed class are not affected.

重转换可能还会修改方法体、常量池和属性表,但是一定不能添加、移除、重命名字段或者方法、修改方法签名或者修改继承关系。这些限制可能会在未来的 Java 版本中被移除。class 字节码在重转换完成之后才会进行检查、校验、安装/应用,如果最终转换后得到的字节码是错误的,这个方法将会抛出一个异常。

如果这个方法抛出了异常,将不会有 classes 被重转换。

5>是否支持重定义 classes

boolean isRedefineClassesSupported()

返回当前 JVM 是否支持重定义 classes。当且仅当在 agent JAR 的 manifest 中存在 Can-Redefine-Classes 属性为 true 且 JVM 拥有重定义 classes 的能力的时候返回 true。

6>重定义 classes

redefineClasses
void redefineClasses(ClassDefinition... definitions)
              throws ClassNotFoundException,
                     UnmodifiableClassException

重定义给定的 class (定义)。

这个方法在无需使用现有 classes 的引用的情况下替换它们的定义,类似在调试代码的时候进行热重载那样重新编译某些类。当某些类需要被转换的时候,retransformClasses 就会被调用。

This method operates on a set in order to allow interdependent changes to more than one class at the same time (a redefinition of class A can require a redefinition of class B).

如果被重定义的方法已经存在一些栈帧,这些栈帧的字节码将会保持和原方法一致不变,而重定义后的方法将会在后续的新的调用中被使用。

This method does not cause any initialization except that which would occur under the customary JVM semantics. In other words, redefining a class does not cause its initializers to be run. The values of static variables will remain as they were prior to the call.

Instances of the redefined class are not affected.

重转换可能还会修改方法体、常量池和属性表,但是一定不能添加、移除、重命名字段或者方法、修改方法签名或者修改继承关系。这些限制可能会在未来的 Java 版本中被移除。class 字节码在重转换完成之后才会进行检查、校验、安装/应用,如果最终得到的字节码是错误的,这个方法将会抛出一个异常。

如果这个方法抛出了异常,将不会有 classes 被重定义。

7>判断一个 class 是否可以被修改

boolean isModifiableClass(Class theClass)

判断一个 class 是否可以通过重转换和重定义来修改,可以则返回 true,否则返回 false。

class 的重转换必须 isRetransformClassesSupported() 为 true;重定义必须要 isRedefineClassesSupported() 为 true。但是这两个方法的返回值不会对当前方法产生影响,这个方法是具体的类维度的定义。

原生类(如 java.lang.Integer.TYPE)以及数组类永远不能被修改

8>获取当前 JVM 已经加载的所有 classes

Class[] getAllLoadedClasses()

9>获取某个类加载器已经初始化的类

Class[] getInitiatedClasses(ClassLoader loader)

获取传入类加载器已经加载的所有类,如果传入为 null,将视为传入了 boostrap 类加载。

10>获取对象的大小

long getObjectSize(Object objectToSize)

返回传入对象的大小,这个大小取决于具体虚拟机的实现,且是一个近似值。这个值仅适合在同一个 JVM 内进行比较,另外这个估算值也是可能会变化的。

11>添加额外的 jar

void appendToBootstrapClassLoaderSearch(JarFile jarfile)
void appendToSystemClassLoaderSearch(JarFile jarfile)
  1. 方法一是添加包含 instrumentation classes 的 jar,当 boostrap 类加载器无法加载某个类的时候,就会在该 jar 中搜索该类进行加载,这个方法可以被调用多次添加多个 jar,避免 jar 中包含除了 instrumentation 之外的 class,导致潜在的类同名加载冲突。一个合适的避免冲突的方法就是给 instrumentation 的类放在一个唯一的包名下。

  2. 方法二是添加包含 instrumentation classes 的 jar,当 system 类加载器无法加载某个类的时候,就会在该 jar 中搜索该类进行加载,这个方法可以被调用多次添加多个 jar,避免 jar 中包含除了 instrumentation 之外的 class,导致潜在的类同名加载冲突。

    当 system 类加载器实现一个名为 appendToClassPathForInstrumentatiion 的、且只有一个 java.lang.String 参数的方法时,才支持这种添加 jar 的方式。JVM 通过 jarfile.getName() 返回的结果传入到该方法的参数中实现添加 jar。

12>isNativeMethodPrefixSupported

13>setNativeMethodPrefix

两种加载 Agent 的方式

我们需要编写一个 agent 类,这个类中包含一些会被 JVM 通过某种方式运行的钩子方法,然后在钩子方法中可以获取到 JVM 传入的 Instrumentation 实例添加我们的自定义类转换器。

静态加载

在启动程序的时候指定 agent:

  1. 将我们写好的 agent 打包成一个 jar,在启动 Java 程序的时候指定一个 JVM 参数 javaagent

    java -javaagent:[=options] -jar 
    

    这个选项可以用在一个命令上使用多次,从而创建多个 agent。多个 agent 可能使用同一个 jar。

  2. 在 agent jar 中的 manifest 添加一项 Premain-class 为我们的 agent 类的全限定名,例如

    Premain-Class : org.example.JavaAgent
    
  3. agent 类中的必须存在以下钩子方法中的一个

    public static void premain(String agentArgs, Instrumentation inst);
    public static void premain(String agentArgs);
    

    JVM 会先尝试寻找前者调用;如果 agent 中没有实现该方法,就会尝试调用后者;如果还找不到,JVM 就会停止运行。第一步中的 options 字符串会传递到 premain 方法的第一个字符串参数中。

静态指定 agent 的方式在程序启动的时候在任何代码执行之前进行字节码修改(先按照 agent 的定义顺序执行 premain 方法,所有 premain 方法执行完成之后才会执行 main 方法)。

agent class 是通过 system class loader 加载的(ClassLoader.getSystemClassLoader)。这个类加载器同时会加载应用的 main 方法类。premain 方法和 main 方法拥有相同的 security 和 classloader rules,没有什么特别的限制,只要是 main 方法中能做的,premain 方法都可以做。

如果无法获取 agent(包括 agent class 无法加载、agent class 没有 premain 方法)或者 premain 方法抛出了异常,JVM 都会停止运行。

动态加载

在 JVM 程序运行之后(main 方法已经运行),通过 Java Attach API 为正在运行的 JVM 进程加载 agent(Arthas)。

  1. 将写好的 agent 类打包在一个 JAR,并在 manifest 中添加一项 Agent-Class ,类似 Premain-Class,值为 agent 类的全限定名。

  2. agent 类必须实现以下之一的钩子方法:

    public static void agentmain(String agentArgs, Instrumentation inst);
    public static void agentmain(String agentArgs);
    

    premain 方法,JVM 会先尝试调用前者然后再尝试调用后者。agentmain 方法的类加载器和 premain 一致,security 和 classloader rules 也一致。

  3. 注意: agent class 中的 agentmain 方法只有在动态加载的方式中会被调用,而且一旦 JVM 已经启动将不会调用 premain 方法;而在通过命令行方式的静态加载 agent,agentmain 方法不会被调用。

  4. 通过 Java Attach API 编写第三个程序代码,该程序会连接到需要被 agent 的 JVM 程序,然后通过类似以下代码使得该程序加载 agent:

    String agentFilePath = "/Users/zhonghongpeng/IdeaProjects/tech-learning/learingsamples/agent/target/test-agent-jar-with-dependencies.jar";            
    VirtualMachine jvm = VirtualMachine.attach(jvmPid);
    jvm.loadAgent(agentFile.getAbsolutePath());
    jvm.detach();
    

agent 的 manifest 属性

  • Premain-Class:当 agent 在 JVM 启动的时候被指定,需要定义该属性为 agent class 的全限定名,也就是包含 premain 方法的类,如果该类没有该方法,JVM 会停止运行。

  • Agent-Class:如果 JVM 的实现提供了在 VM 启动之后加载 agent 的机制,需要通过当前属性指定 agent 类的全限定名,该类包含了 agentmain 方法,否则 JVM 会停止运行。

  • Boot-Class-Path:A list of paths to be searched by the bootstrap class loader. Paths represent directories or libraries (commonly referred to as JAR or zip libraries on many platforms). These paths are searched by the bootstrap class loader after the platform specific mechanisms of locating a class have failed. Paths are searched in the order listed. Paths in the list are separated by one or more spaces. A path takes the syntax of the path component of a hierarchical URI. The path is absolute if it begins with a slash character (‘/’), otherwise it is relative. A relative path is resolved against the absolute path of the agent JAR file. Malformed and non-existent paths are ignored. When an agent is started sometime after the VM has started then paths that do not represent a JAR file are ignored. This attribute is optional.

  • Can-Redefine-Classes:Boolean (true or false, case irrelevant). 是否需要为该 agent 开启 redefine class 的功能. 不是 true 的值都会被当成 false. 可选,默认是 false.

  • Can-Retransform-Classes:Boolean (true or false, case irrelevant). 是否需要为该 agent 开启 rertransform class 的功能. 不是 true 的值都会被当成 false. 可选,默认是 false.

  • Can-Set-Native-Method-Prefix:Boolean (true or false, case irrelevant). Is the ability to set native method prefix needed by this agent. Values other than true are considered false. This attribute is optional, the default is false.

字节码框架选型指标

high-level API or low-level API, community size, and the license

遇到的问题和解决

Maven打包问题

默认情况下,maven 仅对当前工程中的源文件编译出来的字节码打包到一个 jar 中,而不会对其依赖项进行打包。一旦我们直接执行打包出来的文件就会遇到 ClassNotFound 等错误。这里通过一个 maven 插件 maven-assembly-plugin 解决,它会对当前项目默认打出来的包进行重新打包(另外 springboot 也有类似的插件),包含了除了 system scope 之外的所有依赖:

            
                maven-assembly-plugin
                
                    
                        
                            true
                        
                        
                            com.john.agent.MyInstrumentationAgent
                            com.john.application.Launcher
                            true
                            true
                        
                    
                    
                        jar-with-dependencies
                    
                    test-agent
                
            

运行以下命令即可:

mvn assembly:assembly

使用以下命令可以查看需要的 class 是否已经被打在包中:

jar -tf test-agent-jar-with-dependencies.jar | grep xxx

找不到 VirtualMachine 类

AgentLoader 类中我们引入了 com.sun.tools.attach.VirtualMachine 类,IDEA 编译是可以通过的,因为 IDEA 自动扫描的 classpath 包含了该类所属 jar 包 tools.jar 所在路径。

image-20201229115948942

但是在进行 maven 打包的时候编译过不了:

步骤1

通过增加 system 依赖解决

        
            org.sun
            tools
            1.0
            system
            ${JAVA_HOME}/lib/tools.jar
        

以上方式虽然使得 maven 打包的时候可以编译通过但是在运行 jar 文件的时候就找不到了,报了以下错误:

十二月 29, 2020 10:07:37 上午 com.john.agent.MyInstrumentationAgent premain
信息: [Agent] In premain method
class com.john.application.MyAtmsun.misc.Launcher$AppClassLoader@18b4aac2
Exception in thread "main" java.lang.reflect.InvocationTargetException
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at sun.instrument.InstrumentationImpl.loadClassAndStartAgent(InstrumentationImpl.java:386)
        at sun.instrument.InstrumentationImpl.loadClassAndCallPremain(InstrumentationImpl.java:401)
Caused by: java.lang.NoClassDefFoundError: javassist/NotFoundException
        at com.john.agent.MyInstrumentationAgent.transform(MyInstrumentationAgent.java:50)
        at com.john.agent.MyInstrumentationAgent.transformClass(MyInstrumentationAgent.java:31)
        at com.john.agent.MyInstrumentationAgent.premain(MyInstrumentationAgent.java:14)
        ... 6 more
Caused by: java.lang.ClassNotFoundException: javassist.NotFoundException
        at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
        at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
        ... 9 more

看到了一个 java.lang.NoClassDefFoundError 错误,所有的元信息 Error 都是因为在运行时和编译期的元信息(类、方法、属性等)发生了偏差被抛出。这里是因为在编译期 VirtualMachine 类就需要被检查(因为在 AgentLoader.java 源文件中使用了 import 字面量进行导入,在 javac 编译阶段就需要进行检查;相对于那些不会在编译期做检查的元信息错误即抛出 Exception)是否存在,被引用的方法描述是否正确等,在编译期通过后但是在运行期发现通过所有类加载器都无法找到这个类,即抛出一个该 Error

其根本原因是因为在运行时类加载器无法找到 tools.jar 这一个包,而这又是因为上面提到的 assembly maven 插件默认不会将 system scope 的 jar 打到包中,以下有三种方式可以解决:

  1. 增加 assembly.xml 修改 assembly 默认配置:比较繁琐,要引入额外的配置,这里先不选择这种。
  2. 执行 java 程序的时候通过 -cp 指定 tools.jar:我们需要通过 -jar 来运行我们打出来的 full jar,同时一旦指定了 -jar 选项,-cp 就会被忽略,不选择。
  3. 使用 maven-install 插件将本地安装绑定到 clean 阶段,查看下面的步骤2

步骤2

引入以下插件,在 mvn clean 阶段将 tools.jar 安装到本地:

            
                maven-assembly-plugin
                
                    
                        
                            true
                        
                        
                            com.john.agent.MyInstrumentationAgent
                            com.john.agent.MyInstrumentationAgent
                            com.john.application.Launcher
                            true
                            true
                        
                    
                    
                        jar-with-dependencies
                    
                    test-agent
                
            

然后修改依赖为:

        
            org.sun
            tools
            0.1
        

在每一次执行以下命令之前先执行 mvn clean 即可:

mvn assembly:assembly

参考阅读

oracle JVM TI guide

JVM Tool Interface Reference

oracle java.lang.instrument pacakge

oracle java se 8 api document:Package java.lang.instrument

Jrebel:How to write Java agent

baeldung:Guide to Java Instrumentation

包含了静态加载和动态加载实现的代码示例

Understanding Java Agents

Java Programming Tutoriial JNI

Oracle jdk8 JNI


   转载规则


《Java agent》 阿钟 采用 知识共享署名 4.0 国际许可协议 进行许可。
  目录