Java Agent

news2024/10/6 10:28:02

摘要

​ JavaAgent就是Java探针,是一个JVM插件,常用于代码热更新,AOP,JVM监控等功能。这个技术对大多数的同学来说可能有点陌生,但是对Java软件开发人员来说肯定都多多少少接触过,只是相对其原理,我们更多的关注在以它为核心的工具使用上。例如常见的热部署(JRebel, spring-loaded)、各种线上诊断工具(btrace, 阿里的Arthas)、代码覆盖率工具(JaCoCo),如果这些你都没使用过的话,那还有一个工具作为开发人员你一定使用过,就是开发工具IDEA的Debug功能,破解的IDEA在idea.vmoptions里也可以看到-javaagent的配置。 另外现在主流的APM(Application Performance Management应用性能管理)工具如OneAPM、OpenTelemetry等也是基于Agent来实现的。有了这样的功能,开发者就可以实现更为灵活的运行时虚拟机监控和 Java 类操作,这样的特性实际上提供了一种虚拟机级别支持的 AOP 实现方式,使得开发者无需对 JDK 做任何升级和改动,就可以实现某些 AOP 的功能。

​ **关键字:**Instrumentation、JVMTI、JavaAgent

​ 在命令行输入 java可以看到相应的参数,其中有几个和 java agent相关的,要了解怎么使用就要先认识Instrumentation和JVMTI。

-agentlib:<libname>[=<选项>] 加载本机代理库 <libname>, 例如 -agentlib:hprof
	另请参阅 -agentlib:jdwp=help 和 -agentlib:hprof=help
-agentpath:<pathname>[=<选项>]
	按完整路径名加载本机代理库
-javaagent:<jarpath>[=<选项>]
	加载 Java 编程语言代理, 请参阅 java.lang.instrument

一、认识Instrumentation

rt.jar 中定义了一个包,这个包提供了一些工具帮助开发人员在 Java 程序运行时,动态修改系统中的 Class 类型。该路径下有两个重要的类:Instrumentation和ClassFileTransformer。

0

​ Instrumentation是Java提供的一个来自JVM的接口,该接口提供了一系列查看和操作Java类定义的方法,例如修改类的字节码、向classLoader的classpath下加入jar文件等。从 JDK 5 开始,利用JVM提供的 Instrumentation API ,开发者可以构建一个独立于应用程序的代理程序(Agent),使得开发者可以通过Java语言来操作和监控JVM内部的一些状态和监测运行在JVM上的程序,用来对Java程序实现监控分析,甚至能够在对业务代码无侵入的情况下实现对字节码的修改,完成替换和修改某些类的定义,实现一些特殊功能(如AOP、热部署)。在 JDK6 里面,instrumentation 包被赋予了更强大的功能:启动后的 instrument、本地代码(native code)instrument,以及动态改变 classpath 等等。这些改变,意味着 Java 具有了更强的动态控制、解释能力,它使得 Java 语言变得更加灵活多变。   Instrumentation的一些主要方法如下:

public interface Instrumentation {
    /**
     * 注册一个Transformer,从此之后的类加载都会被Transformer拦截。
     * Transformer可以直接对类的字节码byte[]进行修改
     */
    void addTransformer(ClassFileTransformer transformer);
    /**
     * 对JVM已经加载的类重新触发类加载。使用的就是上面注册的Transformer。
     * retransformation可以修改方法体,但是不能变更方法签名、增加和删除方法/类的成员属性
     */
    void retransformClasses(Class<?>... classes) throws UnmodifiableClassException;
    /**
     * 获取一个对象的大小
     */
    long getObjectSize(Object objectToSize);    
    /**
     * 将一个jar加入到bootstrap classloader的 classpath里
     */
    void appendToBootstrapClassLoaderSearch(JarFile jarfile);    
    /**
     * 获取当前被JVM加载的所有类对象
     */
    Class[] getAllLoadedClasses();
}

其中最常用的方法就是addTransformer(ClassFileTransformer transformer)了,这个方法可以在类加载时做拦截,对输入的类的字节码进行修改,其参数是一个ClassFileTransformer接口,定义如下:

/**
 * 传入参数表示一个即将被加载的类,包括了classloader,classname和字节码byte[]
 * 返回值为需要被修改后的字节码byte[]
 */
public interface ClassFileTransformer {
byte[]
transform(  ClassLoader         loader,//要转换的类的定义加载程序
            String              className,//Java虚拟机规范中定义的完全限定类和接口名称的内部形式的类名称
            Class<?>            classBeingRedefined,//如果这是由重定义或重传触发的,则被重定义或重传的类;如果这是类加载,则为null
            ProtectionDomain    protectionDomain,//正在定义或重新定义的类的保护域
            byte[]              classfileBuffer//类格式的输入字节缓冲区——不得修改
         )  throws IllegalClassFormatException;
}

​ addTransformer方法配置之后,后续的类加载都会被Transformer拦截。对于已经加载过的类,可以执行retransformClasses来重新触发这个Transformer的拦截。类加载的字节码被修改后,除非再次被retransform,否则不会恢复。

通过Instrumentation操作或监控一个Java程序的流程如下:

7

二、认识JVMTI

​ “java.lang.instrument”包的具体实现,依赖于 JVMTI。JVMTI(Java Virtual Machine Tool Interface)是一套由 Java 虚拟机提供的,为 JVM 相关的工具提供的本地编程接口集合。JVMTI 是从 Java SE 5 开始引入,整合和取代了以前使用的 Java Virtual Machine Profiler Interface (JVMPI) 和 the Java Virtual Machine Debug Interface (JVMDI),而在 Java SE 6 中,JVMPI 和 JVMDI 已经消失了。JVMTI 提供了一套”代理”程序机制,可以支持第三方工具程序以代理的方式连接和访问 JVM,并利用 JVMTI 提供的丰富的native编程接口,完成很多跟 JVM 相关的功能。开发者可以通过JVMTI向JVM监控状态、执行指令,其目的是开放出一套JVM接口用于 profile、debug、监控、线程分析、代码覆盖分析等工具。
  JVMTI和Instumentation API的作用很相似,都是一套JVM操作和监控的接口,且都需要通过agent来启动:

  • Instumentation API需要打包成jar,并通过Java agent加载(-javaagent)
  • JVMTI需要打包成动态链接库(随操作系统,如.dll/.so文件),并通过JVMTI agent加载(-agentlib/-agentpath)

既然都是agent,那么加载时机也同样有两种:启动时(Agent_OnLoad)和运行时Attach(Agent_OnAttach)。不过相比于Instumentation API,JVMTI的功能强大的多。它是实现Java调试器,以及其它Java运行态测试与分析工具的基础。JVMTI能做的事情包括:

  • 获取所有线程、查看线程状态、线程调用栈、查看线程组、中断线程、查看线程持有和等待的锁、获取线程的CPU时间、甚至将一个运行中的方法强制返回值……
  • 获取Class、Method、Field的各种信息,类的详细信息、方法体的字节码和行号、向Bootstrap/System Class Loader添加jar、修改System Property……
  • 堆内存的遍历和对象获取、获取局部变量的值、监测成员变量的值……
  • 各种事件的callback函数,事件包括:类文件加载、异常产生与捕获、线程启动和结束、进入和退出临界区、成员变量修改、gc开始和结束、方法调用进入和退出、临界区竞争与等待、VM启动与退出……
  • 设置与取消断点、监听断点进入事件、单步执行事件……

前面说的Instumentation API也是基于JVMTI来实现的,具体以addTransformer来说,通过Instrumentation注册的ClassFileTransformer,实际上是注册了JVMTI针对类文件加载事件(ClassFileLoadHook)的callback函数。这个callback函数长这个样子:

void JNICALL
ClassFileLoadHook(jvmtiEnv *jvmti_env,
            JNIEnv* jni_env,
            jclass class_being_redefined,
            jobject loader,
            const char* name,
            jobject protection_domain,
            jint class_data_len,
            const unsigned char* class_data,
            jint* new_class_data_len,
            unsigned char** new_class_data)

参数class_data和new_class_data分别对应了读入的原字节码数组,和提供的修改后的字节码数组的指针。这样,我们在方法的实现中就可以把修改后的类的字节码写回,实现 bytecode instrumentation。
  InstumentationImpl的实现中,在这个callback函数里,对ClassFileTransformer的transform方法再进行一次回调。这样的一次封装,就做到了通过Java语言实现字节码拦截修改的能力。

三、认识JavaAgent

​ Java agent技术的实现是基于Java Instrumentation技术。利用Instrumentation技术,开发者可以构建独立于应用的java agent(代理)程序, 用来监控运行于JVM之上的程序,甚至可以动态修改和替换类的定义。从而在不修改原有应用程序的前提下,动态改变应用程序的行为。

​ Java agent是一种特殊的Java程序(Jar文件),它是Instrumentation的客户端。与普通Java程序通过main方法启动不同,agent并不是一个可以单独启动的程序,而必须依附在一个Java应用程序(JVM)上,与它运行在同一个进程中,通过Instrumentation API与虚拟机交互。Java agent既可以在VM启动时加载,也可以在VM启动后加载,以jar包的形式部署在JVM中,jar文件的manifest需要指定agent的类名。根据不同的启动时机,agent类需要实现不同的方法(二选一):
1

1、启动时加载(Java1.5开始提供):

​ 通过vm的启动参数-javaagent:**.jar来启动。premain 方 法 用 于 在 启 动 时 ,就是运行在 main 函数之前的的类。当Java 虚拟机启动时,在执行 main 函数之前,JVM 会先运行-javaagent所指定 jar 包内 Premain-Class 这个类的 premain 方法 。

/**
 * 以vm参数的形式载入,在程序main方法执行之前执行
 * 其jar包的manifest需要配置属性Premain-Class
 * JVM 会优先加载 带 Instrumentation 签名的方法,加载成功忽略第二种,如果第一种没有,则加载第二种方法。Instrumentation是一个重要的参数。
 */
public static void premain(String agentArgs, Instrumentation inst);

public static void premain(String agentArgs)

对于VM启动时加载的Java agent,premain 方法仅限于应用程序的启动时,即在程序main方法执行之前被调用,此时大部分Java类都没有被加载(“大部分”是因为,agent类本身和它依赖的类还是无法避免的会先加载的,而这些类使用 premain 方法是无法实现字节码改写的。),这时是一个对类加载埋点做手脚(addTransformer)的好机会。如果此时premain方法执行失败或抛出异常,那么JVM的启动会被终止。agent加载时,Java agent的jar包先会被加入到system class path中,然后agent的类会被system class loader加载,这个system class loader就是所在的Java程序的class loader,这样agent就可以很容易的获取到想要的class。Java agent premain的执行流程如下:

21

2、启动后加载(Jdk1.6增加):

​ 在vm启动后的任何时间点,通过Attach API,agentmain 可以在 main 函数开始运行之后再运行,调用java进程,将自己编写的agentmain 注入目标完成对程序的监控,修改。被代理的目标程序VM有可能很早之前已经启动,其所有类已经被加载完成,这个时候需要借助Instrumentation#retransformClasses(Class<?>… classes)让对应的类可以重新转换,从而激活重新转换的类执行ClassFileTransformer列表中的回调。

/**
 * 以Attach的方式载入,在Java程序启动后执行
 * 其jar包的manifest需要配置属性Agent-Class
 */
public static void agentmain(String agentArgs, Instrumentation inst);

public static void agentmain (String agentArgs)

​ 对于VM启动后加载的Java agent,Instrumentation 会通过 agentmain 方法传入程序。agentmain 方法在 main 函数开始运行后才被调用,其最大优势是可以在程序运行期间进行字节码的替换。如果agentmain执行失败或抛出异常,JVM会忽略掉错误,不会影响到正在running的Java程序。Attach API实现动态注入的原理是应用程序通过虚拟机提供的 attach(pid 进程id)方法,可以将代理程序连接(attach)到一个运行中的 Java 进程上,之后便可以通过 loadAgent(AgentJarPath)将 Agent 的 jar 包注入对应的进程,然后对应的进程会调用 agentmain 方法(开源的Java诊断工具BTrace和Alibaba的Arthas也是基于此),如下图所示。

3

​ premain的探针使用方式比较局限,而且每次探针更改的时候,都需要重新启动应用,而agentmain的探针程序就可以直接连接到已经启动的 jvm 中。可以实现例如动态替换类,查看加载类信息的一些功能。虽然premain 方法与 agentmain 方法相比有很大的局限性,但是目前主流的基于探针的监控系统都是基于这种方式实现的对应用的无侵入监控,比如java领域的APM(Application Performance Management应用性能管理)工具,这是因为premain方式也有自己的优势:

  • premain探针:除了名称以外,可以更改任意内容,名称改了,ClassLoad 就会出问题
  • agentmain探针:不能修改Class的文件结构,即不能添加方法,不能添加字段,只能修改方法体的内容,否则就会报UnsupportedOperationException: class redefinition failed: attempted to change the schema (add/remove fields)类似的异常

四、JavaAgent实现方式

​ 要想使用JavaAgent完成我们的需求,我们需要在transform方法的实现中,对指定的类,做指定的字节码增强。通常来说,做字节码增强都需要使用到一些操作字节码的框架,比如ASM,CGLIB,Byte Buddy,Javassist。当然你非常厉害的话也可以直接用位运算操作byte[],不需要任何框架,例如JDK反射(method.invoke())的实现,用位操作拼装出一个类。

@Override
public byte[] transform(ClassLoader loader,
                        //要转换的类的定义加载程序,
                        String className,
                        //Java虚拟机规范中定义的完全限定类和接口名称的内部形式的类名称。
                        // 例如,“java/util/List”。
                        Class<?> classBeingRedefined,
                        //如果这是由重定义或重传触发的,则被重定义或重传的类;如果这是类加载,则为null
                        ProtectionDomain protectionDomain,
                        //正在定义或重新定义的类的保护域
                        byte[] classfileBuffer
                        //类格式的输入字节缓冲区——不得修改
) throws IllegalClassFormatException {
    ClassReader classReader = new ClassReader(classfileBuffer);
    PreClassVisitor preClassVisitor = new PreClassVisitor( new ClassWriter(ClassWriter.COMPUTE_MAXS));
    classReader.accept(preClassVisitor,0);
    return preClassVisitor.toByteArray();
}

相对来说,操作字节码的高手可能更喜欢ASM,因为它提供的方法更底层,功能更强大更直白。对于字节码不熟悉的开发者,更适合javassist,它可以直接以Java代码方式直接修改方法体。下面分别举例使用Javassist结合premain方式完成对方法的耗时计算,使用ASM结合agentmain attach 完成对方法中数据的更换。

(一)premain和Javassist

1、新建maven项目做测试
package org.example;
/**
 * 测试服务App,简单的启动hello方法
 */
public class App {
    public static void main(String[] args) throws InterruptedException {
        hello();
        Thread.sleep((long) (Math.random() * 10));//随机暂停0-10ms
    }
    public static void hello() throws InterruptedException {
        System.out.println("The number six is 6");
        Thread.sleep((long) (Math.random() * 100));//随机暂停0-100ms
    }
}

2、新建一个agent项目,没有main方法,只有premain方法
package org.example;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;
/**
 * 探针类
 */
public class PerfMonAgent {
    static private Instrumentation inst = null;
    /**
     * 在App服务main方法调用之前会先调用此方法
     * 与main方法运行在同一个JVM中,并被同一个System ClassLoader装载
     **/
    public static void premain(String agentArgs, Instrumentation _inst) {
        System.out.println("org.example.PerfMonAgent.premain() was called.");
        inst = _inst;
        ClassFileTransformer trans = new PerfMonXformer();
        System.out.println("Adding a org.example.PerfMonXformer instance to the JVM.");
        inst.addTransformer(trans);
    }

}
package org.example;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.CtNewMethod;
import java.lang.instrument.ClassFileTransformer;
import java.security.ProtectionDomain;

/**
 * javassist 改造类
 */
public class PerfMonXformer implements ClassFileTransformer {
    final static String prefix = "\nlong startTime = System.currentTimeMillis();\n";
    final static String postfix = "\nlong endTime = System.currentTimeMillis();\n";

    @Override
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
                            ProtectionDomain protectionDomain, byte[] classfileBuffer) {
        //java自带的方法不进行处理
        if (!className.endsWith("App")) {
            return null;
        }
        className = className.replace("/", ".");
        CtClass ctclass = null;
        try {
            ctclass = ClassPool.getDefault().get(className);// 使用全称,用于取得字节码类<使用javassist>
            for (CtMethod ctMethod : ctclass.getDeclaredMethods()) {
                String methodName = ctMethod.getName();
                String newMethodName = methodName + "$old";// 新定义一个方法叫做比如sayHello$old
                ctMethod.setName(newMethodName);// 将原来的方法名字修改
                // 创建新的方法,复制原来的方法,名字为原来的名字
                CtMethod newMethod = CtNewMethod.copy(ctMethod, methodName, ctclass, null);
                // 构建新的方法体
                StringBuilder bodyStr = new StringBuilder();
                bodyStr.append("{");
                bodyStr.append("System.out.println(\"==============Enter Method: " + className +
                        "." + methodName + " ==============\");");
                bodyStr.append(prefix);
                bodyStr.append(newMethodName + "($$);\n");// 调用原有代码,类似于method();($$)表示所有的参数
                bodyStr.append(postfix);
                bodyStr.append("System.out.println(\"==============Exit Method: " + className +
                        "." + methodName + " Cost:\" +(endTime - startTime) +\"ms " + "===\");");
                bodyStr.append("}");

                newMethod.setBody(bodyStr.toString());// 替换新方法
                ctclass.addMethod(newMethod);// 增加新方法
            }
            return ctclass.toBytecode();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}
3、pom文件,除了要引入javassist依赖,最主要的是要配置以下内容
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <configuration>
                    <archive>
             			 <!--自动添加META-INF/MANIFEST.MF -->
            						<manifest>
               						 <addClasspath>true</addClasspath>
            						</manifest>
                        <manifestEntries>
                            <Premain-Class>org.example.PerfMonAgent</Premain-Class>
                            <Can-Redefine-Classes>true</Can-Redefine-Classes>
                            <Can-Retransform-Classes>true</Can-Retransform-Classes>
                        </manifestEntries>
                    </archive>
                </configuration>
            </plugin>

这个maven配置在打包时默认会在打包的文件中生成一个MANIFREST.MF文件,在该文件中主要定义了程序运行相关的配置信息,程序运行前会先检测该文件中的配置项:

Manifest-Version: 1.0
Can-Redefine-Classes: true//true表示能重定义此代理所需的类,默认值为 false(可选)
Can-Retransform-Classes: true//true 表示能重转换此代理所需的类,默认值为 false (可选)
Premain-Class: org.example.PerfMonAgent //包含 premain 方法的类(类的全路径名)

MANIFREST.MF也可以手动在 resources 目录下新建目录:META-INF,在该目录下新建文件:MANIFREST.MF,手动编写以上配置,除了这些还有一些关于项目信息的配置。

4、将agent项目打包,然后在idea里添加VM启动参数,启动App的main方法

4

-javaagent: 后面是agent项目的包路径:

-javaagent:/Users/gaoruiqiang/DataPhant/agentdemo/target/agentdemo.jar

整体为:

java -javaagent:/Users/gaoruiqiang/DataPhant/agentdemo/target/agentdemo.jar -jar app.jar

一个java程序中-javaagent参数的个数是没有限制的,所以可以添加任意多个javaagent。所有的java agent会按照你定义的顺序执行,例如:

java -javaagent:agent1.jar -javaagent:agent2.jar -jar app.jar

程序执行的顺序将会是:

Agent1.premain -> Agent2.premain -> App.main

App启动后控制台打印出了以下信息,可以看到在方法的前后都有打印信息,最后计算出了方法的耗时,这就是使用premain实现了AOP。

5

(二)agentmain和ASM

1、新建个maven项目做测试
package org.example;
import java.util.concurrent.TimeUnit;
/**
 * 对foo方法做修改
 */
public class MyTestMain {
    public static void main(String[] args) throws InterruptedException {
        while (true) {
            System.out.println(foo());
            TimeUnit.SECONDS.sleep(3);
        }
    }
    public static int foo() {
        return 100; // 修改后 return 50;
    }
}
2、跟premain函数一样,编写一个含有agentmain函数的 Java 类:
package org.example;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.commons.AdviceAdapter;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;
import java.security.ProtectionDomain;

import static org.objectweb.asm.Opcodes.ASM7;


/**
 * agentmain方法
 */
public class AgentMain {
  	//AgentMain运行的JVM链接到MyTestMain运行的JVM后,就可以通过此方法做字节码增强
    public static void agentmain(String agentArgs, Instrumentation inst) throws ClassNotFoundException, UnmodifiableClassException {
        System.out.println("agentmain called");
        inst.addTransformer(new MyClassFileTransformer(), true);
        Class classes[] = inst.getAllLoadedClasses();
        for (int i = 0; i < classes.length; i++) {
            if (classes[i].getName().equals("org.example.MyTestMain")) {
                System.out.println("Reloading: " + classes[i].getName());
                inst.retransformClasses(classes[i]);
                break;
            }
        }
    }
  
      public static class MyClassFileTransformer implements ClassFileTransformer {
        @Override
        public byte[] transform(ClassLoader loader, String className, Class<?> 		  classBeingRedefined,ProtectionDomain protectionDomain, byte[] bytes) throws IllegalClassFormatException {
          //匹配类名后缀
            if (!className.endsWith("MyTestMain")) return bytes;
            ClassReader cr = new ClassReader(bytes);
            ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES);
            ClassVisitor cv = new MyClassVisitor(cw);
            cr.accept(cv, ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG);
            return cw.toByteArray();
        }
    }
  	//操作类
    public static class MyClassVisitor extends ClassVisitor {
        public MyClassVisitor(ClassVisitor classVisitor) {
            super(ASM7, classVisitor);
        }
        @Override
        public MethodVisitor visitMethod(int access, String name, String descriptor,
                                         String signature, String[] exceptions) {
            MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
            // 只转换 foo 方法
            if ("foo".equals(name)) {
                return new MyMethodVisitor(mv, access, name, descriptor);
            }
            return mv;
        }
    }
  //操作方法
      public static class MyMethodVisitor extends AdviceAdapter {
        protected MyMethodVisitor(MethodVisitor mv, int access, String name, String desc) {
            super(ASM7, mv, access, name, desc);
        }
        @Override
        protected void onMethodEnter() {
            // 在方法开始插入 return 50;
            mv.visitIntInsn(BIPUSH, 50);
            mv.visitInsn(IRETURN);
        }
    }
}
3、pom文件打包配置要配置 ,将AgentMain服务打包为agentmain.jar
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <configuration>
                    <archive>
                        <!--自动添加META-INF/MANIFEST.MF -->
            						<manifest>
               						 <addClasspath>true</addClasspath>
            						</manifest>
                        <manifestEntries>
                            <!--Agent-Class 这个是使用attach方式时配置-->
                            <Agent-Class>org.example.AgentMain</Agent-Class>
                            <Can-Redefine-Classes>true</Can-Redefine-Classes>
                            <Can-Retransform-Classes>true</Can-Retransform-Classes>
                        </manifestEntries>
                    </archive>
                </configuration>
            </plugin>

在MANIFEST.MF配置环境参数

Manifest-Version: 1.0
Can-Redefine-Classes: true//true表示能重定义此代理所需的类,默认值为 false(可选)
Can-Retransform-Classes: true//true 表示能重转换此代理所需的类,默认值为 false (可选)
Agent-Class:org.example.AgentMain	//指定代理类,使用attach方式会生成
4、需要有个服务将agentmain.jar attach到 MyTestMain 的jvm 进程,执行AgentMain方法
package org.example;

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;

import java.io.IOException;
import java.util.List;

public class TestAgentMain {

    public static void main(String[] args) throws IOException, AttachNotSupportedException,
            AgentLoadException, AgentInitializationException, InterruptedException {
        //获取当前系统中所有 运行中的 虚拟机
        System.out.println("running JVM start ");
        hello();
        List<VirtualMachineDescriptor> list = VirtualMachine.list();
        for (VirtualMachineDescriptor vmd : list) {
            //如果虚拟机的名称为 xxx 则 该虚拟机为目标虚拟机,获取该虚拟机的 pid
            //然后加载 agentmain.jar 发送给该虚拟机
            System.out.println(vmd.displayName());
            if (vmd.displayName().equals("org.example.MyTestMain")) {
                VirtualMachine virtualMachine = VirtualMachine.attach(vmd.id());
                try {
             virtualMachine.loadAgent("/Users/gaoruiqiang/DataPhant/agentdemo/target/agentmain.jar");
                } finally {
                    virtualMachine.detach();
                }
            }
        }
    }
}

5、将agentmain项目打包成agentmain.jar后,先启动 MyTestMain ,然后再启动TestAgentMain

9

可以看到,在TestAgentMain 启动之前打印的是100,启动之后,打印的是50,这就实现用agentmain方式在服务启动后对代码参数做修改。

五、Java探针技术的成熟应用

​ 在现代IT系统中,尤其是云原生、微服务系统,一次外部请求往往需要多个内部服务、多个中间件和多台机器的相互调用才能完成。在这一系列的调用中,任何阶段出现的问题都可能导致外部服务失败或延迟升高,影响用户体验。如果想要精确定位及分析问题,需要使用分布式链路追踪技术。分布式链路追踪(Distributed Tracing,简称Trace)可提供整个服务调用链路的调用关系、延迟、结果等信息,非常适用于云原生、分布式、微服务等涉及多个服务交互的系统。

​ Java字节码探针技术是一个非常成熟的应用性能监控技术,领先的应用性能管理软件均采用这一技术。这是一个非常成熟的实现方案。在Gartner出具的APM魔力象限报告中,处于领导者象限的三个APM产品DynaTrace、AppDynamics、NewRelic都是采用Java字节码探针技术。相反采用网络镜像技术的IBM、 Riverbed,由于技术局限性都被定义在了特定领域者范畴。 以下是一些成熟的APM工具:

  • OneAPM :是国内首家支持 Java, .NET, PHP,Ruby,Python,Node.js 的应用性能管理云解决方案,通过 SaaS 向用户提供统一的入口、友好的界面、便捷的流程,来管理和监控应用程序的性能,并完成从前端、到网络、直至应用代码的端到端应用性能管理。

  • OpenTelemetry:是目前全球公认的分布式链路追踪标准,兼容OpenTracing、OpenCensus的各类客户端。OpenTelemetry由一组API、SDK和工具组成,用来观察、生成、采集和导出各类可观测性数据(Traces、Logs、Metrics)。OpenTelemetry只提供数据的格式定义、产生、收集、发送,但并不提供分析、可视化、告警等功能。

  • zipkin:Twitter公司开源的一个分布式追踪工具,被Spring Cloud Sleuth集成,使用广泛而稳定

  • skywalking:开源的一款分布式追踪,分析,告警的工具,现在是Apache旗下开源项目

  • cat:大众点评开源的一款分布式链路追踪工具。

  • New Relic

  • Dynatrace

  • Cisco AppDynamics

  • 阿里巴巴鹰眼

  • 。。。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/510036.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Selenium技术在CentOS6.8系统的腾讯云服务器上的相关使用

目录 一、解释说明二、操作过程中Linux相关命令1、下载谷歌浏览器2、查看谷歌浏览器的版本3、下载对应版本的谷歌驱动&#xff08;或者本地上传&#xff09;4、解压下载的文件5、移动下载文件6、给予文件执行权限7、更新pip3到最高版本8、下载Selenium第三方库9、正式测试10、最…

C#学习笔记--由浅至深理解IEnumerable和IEnumerator

目录 前言总结 IEnumerable 和 IEnumeratorIEnumerable是什么&#xff1f;IEnumerator是什么&#xff1f;总结 结尾预告 前言 上篇文章我是自己实现了一个容器C#学习笔记–实现一个可以重复权重并且能够自动排序的容器–MultiplySortedSet 写完之后突然想到一个问题&#xff…

PMP课堂模拟题目及解析(第6期)

51. 管理层将一个国际项目分配给一位新项目经理。这是该项目经理第一次与团队合作&#xff0c;团队成员位于两个国家&#xff0c;数量平均分布&#xff0c;一个团队由最合适作为个人工作的成员组成&#xff0c;另一个团队由最适合作为团队工作的成员组成。项目经理该怎么做&am…

抖音商城小程序搭建的注意事项

抖音商城小程序已经成为了越来越多电商企业的选择&#xff0c;毕竟它具有强大的用户资源和社交传播力。但是&#xff0c;在搭建抖音商城小程序的过程中&#xff0c;还有一些需要注意的事项。 1、制定明确的策略和目标 在搭建抖音商城小程序前&#xff0c;必须事先制定明确的策…

65.网站个性框架

之前介绍过集中个性网站 严肃优雅型极简主义普通/中性大胆/自信平静祥和创业/上进俏皮/好玩 严肃优雅型 概述 奢华和优雅的设计&#xff0c;基于细小的衬线字体、金色或粉色的颜色和高质量的大图片。 行业 房地产、高端时尚、饰品类&#xff0c;奢侈产品或服务 排版 有衬…

(构造函数的补充2)类型转换与临时变量(新对象)的生成与explicit关键字,类的静态成员变量与成员函数及其应用

(构造函数的补充2)类型转换与临时变量(新对象)的生成与explicit关键字 如果说一个构造函数是单个参数&#xff0c;或者说有多个参数但是第一个参数没有默认值&#xff0c;而其余均有默认值&#xff0c;这时候就特别需要注意类型转换的问题。首先必须得知道的一点就是说一旦有类…

Vue中的键盘事件

目录 一、Vue中的键盘事件的类型 二、keycode指定具体的按键 三、Vue中常用的按键别名 四、Vue自定义按键 五、实现多个按键一起触发事件 一、Vue中的键盘事件的类型 keydown:键盘按下就会触发 keyup:键盘抬起就会触发 二、keycode指定具体的按键 在键盘中&#xff0c;每一…

JMeter测试工具设置中文

JMeter测试工具设置中文 在JMeter中&#xff0c;默认情况下是英文的&#xff0c;如果需要使用中文进行测试&#xff0c;则需要进行如下设置&#xff1a; 第一步、设置JMeter语言选项 在JMeter运行时界面的“Options”菜单中&#xff0c;选择“Choose Language”&#xff0c;…

2023.05.10- 使用Loopback Scaler来改善Stable Diffusion的绘图效果

简介 Loopback Scaler可以添加画面细节 渣图挽救者&#xff1a;在图生图中对原图迭代重绘&#xff0c;修复渣图。脚本在多个循环中处理输入图像&#xff0c;每个循环提高分辨率并优化图像质量。然后&#xff0c;一个循环的图像结果作为下一个循环的输入图像插入&#xff0c;该…

FE_Vue框架的执行流程详解

1 分析脚手架结构 2 整个流程 执行npm run serve&#xff0c;随后来到src中找到【整个项目的入口文件】main.js&#xff0c;这个js页面中引入了Vue、App.vue、关闭了提示等。 // ps: 该文件是整个项目的入口文件 // step1 引入Vue import Vue from vue // step2 引入App组件-…

Android9.0 Charles 模拟器抓包

目录 只想做条安静的咸鱼&#xff0c;混吃等死又一天 一、下载并安装配置Charles 二、下载安装Postern 三、测试抓包 一、下载并安装配置Charles 1.Charles下载网址&#x1f447; Charles Web Debugging Proxy • HTTP Monitor / HTTP Proxy / HTTPS & SSL Proxy / Rev…

EasyPoi导出,设置不同列的字体颜色(修改easypoi 3.1.0版本源码)

声明&#xff1a;代码以esaypoi 3.1.0版本为基础&#xff0c;基本上新的代码都是直接copy源码的&#xff0c;只是稍微修改了源码&#xff1b;仍然需要导入该依赖是 <dependency><groupId>cn.afterturn</groupId><artifactId>easypoi-base</artifac…

数据建模三范式说明

三范式&#xff1a; 1.原子性&#xff1a;属性不可分割 2.唯一性&#xff1a;不能存在部分依赖 3.非冗余性&#xff1a;要求任何字段不能由其他字段派生出来、确保每列都和主键列直接相关,而不是间接相关)

案例|九江银行Zabbix监控系统实践

Zabbix监控平台建设历程 九江银行Zabbix监控系统实践&#xff0c;共分为三个部分&#xff1a; 1.Zabbix监控平台的建设历程 2.Zabbix实践经验分享 3.对未来监控的展望。 项目背景 建立新的一体化基础监控平台。为适应数字化转型的需要、新技术发展的需要及业务连续性的需…

关于使用Lombok的注意事项

文章目录 1、Lombok简介2、使用Lombok的问题2.1 驼峰问题2.2 相同的字符串不同的大小写 3、关于使用Lombok的总结4、写在最后 1、Lombok简介 Lombok项目是一个Java库&#xff0c;它会自动插入您的编辑器和构建工具中&#xff0c;从而为你优化Java代码。通过注解的方式代替我们手…

注入攻击(二)--------HTML(有源码)

前序文章 注入攻击&#xff08;一&#xff09;--------SQL注入(结合BUUCTF sqli-labs) 目录 示例网站搭建1.搭建LAMP开发环境1. MySQL2. PHP3. Apache 写在示例前示例1.反射型HTML注入页面效果源码 示例2.钓鱼表单页面效果源码 示例3.存储型HTML注入页面效果源码 示例网站搭建 …

【Docker】使用 Docker 部署 Maven 仓库

在本文中&#xff0c;将介绍如何使用 Docker 部署一个 Maven 本地私服。Maven 私服可以帮助我们管理和共享本地的 Maven 依赖&#xff0c;提高开发效率。本文将使用 Sonatype Nexus 作为 Maven 私服&#xff0c;并使用 Docker Compose 来部署和管理容器。 准备工作 在开始之前…

mysql数据库在windows服务器下安装

一、mysql安装包下载 官网下载地址&#xff1a;mysql安装包下载 如图所示&#xff1a; 二、配置my.ini文件 解压后的文件尽量不要放在C盘&#xff08;内存小&#xff09;&#xff0c;解压后如下图所示 在上图所示根目录下配置my.ini文件 1、右键创建一个文本&#xff08;.text…

pikachu靶场-Unsafe Upfileupload

文件上传漏洞简述 什么是文件上传漏洞&#xff1f;  ​ 凡是存在文件上传的地方均有可能存在文件上传漏洞&#xff0c;关于上传文件操作的时候对方代码写的是否完整、是否安全&#xff0c;一旦疏忽了某个地方可能会造成文件上传漏洞。 文件上传的原理 网站Web应用都有一些文件…

1.SpringBoot基础篇

SpringBoot 文档更新日志 版本更新日期操作描述v1.02021/11/14A基础篇 前言 ​ 很荣幸有机会能以这样的形式和互联网上的各位小伙伴一起学习交流技术课程&#xff0c;这次给大家带来的是Spring家族中比较重要的一门技术课程——SpringBoot。一句话介绍这个技术&#xff0c;…