`
城的灯
  • 浏览: 152365 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

关于JavaAgent的一点思路

    博客分类:
  • 2015
阅读更多

原文我发布在:www.yangguo.info

最近对Java的ProfilingDebugging非常感兴趣,特别是对线上问题的定位方案进行了较深的调研和分析。因为在SOA架构体系中线上问题经常在测试环境不能复现,所以问题的定位具有非常大的挑战。我将业界定位问题的工具和方案都大概的研究了一遍,不论是JProfilerYourKitBTrace,还是更底层的Serviceability技术......都广泛用到了Agent技术。

在开始之前有必要将HotSpot JVM的Serviceability做个简单的说明。Serviceability可以翻译为服务性,它可以理解为一种能力。这种能力表现在HotSpot虚拟机具备跟踪调试Java进程的能力,可以针对故障分析和隔离。当然作为一名Java程序员,绝大多数时候我们都不需要关注到此层面,但是当我们需要做一个更加深层次的调试工作时我们就绕不开Serviceability,具体的技术有如下几项。

  1. The Serviceability Agent(SA).Sun私有的一个组件,它可以帮助开发者调试Hotspot。
  2. jvmstat performance counters.HotSpot通过Sun私有的共享内存机制实现的性能计数器,它是公开给外部给外部进程的。它们也称为perfdata
  3. The Java Virtual Machine Tool Interface (JVM TI).它是一套标准的C接口,是 JSR 163 - JavaTM Platform Profiling Architecture 参考实现。JVMTI提供了可用于 debug和profiler的接口;同时,在Java 5/6 中,虚拟机接口也增加了监听(Monitoring),线程分析(Thread analysis)以及覆盖率分析(Coverage Analysis)等功能。JVMTI并不一定在所有的Java虚拟机上都有实现,不同的虚拟机的实现也不尽相同。不过在一些主流的虚拟机中,比如 Sun 和 IBM,以及一些开源的如 Apache Harmony DRLVM 中,都提供了标准 JVMTI 实现。
  4. The Monitoring and Management interface.Sun的私有实现,它能够实现对HotSpot的监控和管理。
  5. Dynamic Attach.Sun的私有机制,它允许外部进程在HotSpot中启动一个agent线程,该agent可以将该HotSPot的信息发送给外部进程。
  6. DTrace.全称Dynamic Tracing,也称为动态跟踪,是由Sun™开发的一个用来在生产和试验性生产系统上找出系统瓶颈的工具,可以对内核(kernel)和用户应用程序(user application)进行动态跟踪并且对系统运行不构成任何危险的技术。在任何情况下它都不是一个调试工具,而是一个实时系统分析寻找出性能及其他问题的工具。DTrace是个特别好的分析工具,带有大量的帮助诊断系统问题的特性。还可以使用预先写好的脚本利用它的功能。用户也可以通过使用 DTrace D语言创建他们自己定制的分析工具,以满足特定的需求。除Solaris系列以外,Dtrace已先后被移植到FreeBSD、NetBSD及Max OS X等操作系统上
  7. pstack support.pstack是从Solaris发源的实用程序,可以查看core dump文件,调试进程,查看线程运行情况等等。现在,pstack工具也移植到了Linux系统中,比如Red Hat Linux系统、Ubuntu Linux系统等等。HotSpot允许pstack显示Java堆栈帧。

在这一系列的技术中,SAJVM TI容易产生混淆,SA主要是给JVM开发人员调试虚拟机用的,JVM TI是为Java应用层开发者而设计的,它是标准API,主要用来开发各种调试和分析工具。也就是说SA更底层,如果不触碰JVM虚拟机底层开发,其实绝大多数时候是用不到的。当然也不是说,我们平常完全不会用到SA的功能。比如,jstack -F/m就会使用到SA,我们平常不加-F/-m的时候默认走的是Instrumentationattach操作。jmap中的部分功能也是SA实现的,具体的没有详细研究。Attach是进程内操作,SA是进程外操作,这便是他们的区别之处。大家可能碰见过这样的例子,就是Java进程hung住了,使用jstack是拿不到threaddump的,这个时候只有加-F才可以拿到,并且拿到的dump数据跟不加-F拿到的数据是有差别的。那是因为attach进JVM之后,要拿到threaddump,需要JVM执行操作才行,但是这个时候JVM已经不能响应任何操作了,所以拿不到。但是进程外就不一样了,它是以一个观察者的方式去查看目标进程,所以能够拿到想要的信息。

虽然Serviceability很强大,但是本篇文章真正想讲的是Instrumentation特性。虽然JDK5中就已经有了premain,但是真正具有划时代意义的还是JDK6中的agentmain。因为当不容易重现的问题出现时,你可以不用重启应用,便可以对应用进行问题定位和调试,当然目前只有HotSpotJRockit虚拟机支持。下面给一个通过环绕织入来实现获取方法执行时间的例子。

1.首先来一个注解,它是用在方法上,表示该方法我需要织入代码来获取执行时间。

package info.yangguo.demo.attch_api.test4;

public @interface Point {
}

2.来一个被织入的测试代码,为了方便测试,classpath动态加载javassist,因为该类库在我后面类的transform中使用到了。

package info.yangguo.demo.attch_api.test4;

import java.io.File;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Random;

public class TestExample {
    private Random random = new Random();

    @Point
    public void doSleep() {
        try {
            Thread.sleep(15000);
            System.out.println("*");
        } catch (InterruptedException e) {
        }
    }

    @Point
    private void doTask() {
        try {
            Thread.sleep(random.nextInt(1500));
            System.out.println(".");
        } catch (InterruptedException e) {
        }
    }

    @Point
    public void doWork() {
        for (int i = 0; i < random.nextInt(12); i++) {
            doTask();
        }
    }

    public static void main(String[] args) {
        /**
         * 为了减少设置classpath的做法,具体的类库路径需要自己设置
         */
        ExtClasspathLoader.loadClasspath("/Users/yangguo/.gradle/caches/modules-2/files-2.1/org.javassist/javassist/3.20.0-GA/a9cbcdfb7e9f86fbc74d3afae65f2248bfbf82a0");
        TestExample test = new TestExample();
        while (true) {
            test.doWork();
            test.doSleep();
        }
    }


    /**
     * 根据properties中配置的路径把jar和配置文件加载到classpath中。
     *
     * @author guo.yang
     */
    private static class ExtClasspathLoader {
        /**
         * URLClassLoader的addURL方法
         */
        private static Method addURL = initAddMethod();

        private static URLClassLoader classloader = (URLClassLoader) ClassLoader.getSystemClassLoader();

        /**
         * 初始化addUrl 方法.
         *
         * @return 可访问addUrl方法的Method对象
         */
        private static Method initAddMethod() {
            try {
                Method add = URLClassLoader.class.getDeclaredMethod("addURL", new Class[]{URL.class});
                add.setAccessible(true);
                return add;
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        private static void loadClasspath(String filepath) {
            File file = new File(filepath);
            loopFiles(file);
        }

        private static void loadResourceDir(String filepath) {
            File file = new File(filepath);
            loopDirs(file);
        }

        /**
         * 循环遍历目录,找出所有的资源路径。
         *
         * @param file 当前遍历文件
         */
        private static void loopDirs(File file) {
            // 资源文件只加载路径
            if (file.isDirectory()) {
                addURL(file);
                File[] tmps = file.listFiles();
                for (File tmp : tmps) {
                    loopDirs(tmp);
                }
            }
        }

        /**
         * 循环遍历目录,找出所有的jar包。
         *
         * @param file 当前遍历文件
         */
        private static void loopFiles(File file) {
            if (file.isDirectory()) {
                File[] tmps = file.listFiles();
                for (File tmp : tmps) {
                    loopFiles(tmp);
                }
            } else {
                if (file.getAbsolutePath().endsWith(".jar") || file.getAbsolutePath().endsWith(".zip")) {
                    addURL(file);
                }
            }
        }

        /**
         * 通过filepath加载文件到classpath。
         *
         * @param file 文件路径
         * @return URL
         * @throws Exception 异常
         */
        private static void addURL(File file) {
            try {
                addURL.invoke(classloader, new Object[]{file.toURI().toURL()});
            } catch (Exception e) {
            }
        }
    }
}

3.当然少不了最重要的Transform,在这里面我使用javassist动态修改了字节码,实现了对目标方法的环绕织入。更多更强大的功能就得各位的具体需求了,比如我们还可以在此处加一个监控的report功能,从而实现实时监控。

package info.yangguo.demo.attch_api.test4;

import javassist.ByteArrayClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;

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

public class Transformer implements ClassFileTransformer {
    private ClassPool classPool;

    public Transformer() {
        classPool = new ClassPool();
        classPool.appendSystemPath();
        try {
            classPool.appendPathList(System.getProperty("java.class.path"));
        } catch (Exception e) {
            System.out.println("异常:" + e);
            throw new RuntimeException(e);
        }
    }

    public byte[] transform(ClassLoader loader, String fullyQualifiedClassName, Class<?> classBeingRedefined,
                            ProtectionDomain protectionDomain, byte[] classBytes) throws IllegalClassFormatException {
        String className = fullyQualifiedClassName.replace("/", ".");

        classPool.appendClassPath(new ByteArrayClassPath(className, classBytes));

        try {
            CtClass ctClass = classPool.get(className);
            if (ctClass.isFrozen()) {
                System.out.println("Skip class " + className + ": is frozen");
                return null;
            }

            if (ctClass.isPrimitive() || ctClass.isArray() || ctClass.isAnnotation()
                    || ctClass.isEnum() || ctClass.isInterface()) {
                System.out.println("Skip class " + className + ": not a class");
                return null;
            }

            boolean isClassModified = false;
            for (CtMethod method : ctClass.getDeclaredMethods()) {
                // if method is annotated, add the code to measure the time
                if (method.hasAnnotation(Point.class)) {
                    if (method.getMethodInfo().getCodeAttribute() == null) {
                        System.out.println("Skip method " + method.getLongName());
                        continue;
                    }
                    System.out.println("Instrumenting method " + method.getLongName());
                    //此处实现了一个AOP的环绕织入,在我们常规的需求中,可以在新增监控的report逻辑,
                    //如果需要加载第三方的jar,可以通过动态加载classpath来实现

                    method.addLocalVariable("__metricStartTime", CtClass.longType);
                    method.insertBefore("__metricStartTime = System.currentTimeMillis();");
                    method.insertAfter("System.out.println( System.currentTimeMillis() - __metricStartTime);");
                    isClassModified = true;
                }
            }
            if (!isClassModified) {
                return null;
            }
            return ctClass.toBytecode();
        } catch (Exception e) {
            System.out.println("Skip class : " + className + ",Error Message:" + e.getMessage());
            return null;
        }
    }
}

4.Agent代码主要是设置transformerretransformClasses

package info.yangguo.demo.attch_api.test4;


import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;
import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;

public class Agent {
    public static void agentmain(String agentArguments, Instrumentation instrumentation) throws UnmodifiableClassException {
        RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean();
        System.out.println("Runtime:" + runtimeMxBean.getName() + " : " + runtimeMxBean.getInputArguments());
        System.out.println("Starting agent with arguments " + agentArguments);

        instrumentation.addTransformer(new Transformer(),true);
        instrumentation.retransformClasses(TestExample.class);
    }
}

5.为了方便加了一个自动打包的类,自动生成agent jar,主要是对TransformerAgent打包,并设置Manifest。这样做的好处是可以通过代码自动化,方便二次开发成内部工具。

package info.yangguo.demo.attch_api.test4;


import javax.tools.JavaCompiler;
import javax.tools.ToolProvider;
import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;

/**
 * Created by IntelliJ IDEA
 * User:杨果
 * Date:15/7/8
 * Time:上午10:26
 * <p/>
 * Description:
 */
public class JavacCompileAndJar {
    String javaSourcePath;
    String javaClassPath;
    String targetPath;
    String jarClassPath;
    HashMap<Object, Object> attributes;

    public JavacCompileAndJar(String javaSourcePath, String javaClassPath, String jarClassPath, String targetPath, HashMap attributes) {
        this.javaSourcePath = javaSourcePath;
        this.javaClassPath = javaClassPath;
        this.targetPath = targetPath;
        this.jarClassPath = jarClassPath;
        this.attributes = attributes;
    }

    public void complier() throws IOException {
        System.out.println("*** --> 开始编译java源代码...");

        File javaclassDir = new File(javaClassPath);
        if (!javaclassDir.exists()) {
            javaclassDir.mkdirs();
        }

        List<String> javaSourceList = new ArrayList<>();
        getFileList(new File(javaSourcePath), javaSourceList);

        JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
        int result = -1;
        for (int i = 0; i < javaSourceList.size(); i++) {
            if (javaSourceList.get(i).endsWith("java")) {
                result = javaCompiler.run(null, null, null, "-d", javaClassPath, javaSourceList.get(i));
                System.out.println(result == 0 ? "*** 编译成功 : " + javaSourceList.get(i) : "### 编译失败 : " + javaSourceList.get(i));
            }
        }
        System.out.println("*** --> java源代码编译完成。");
    }

    private void getFileList(File file, List<String> fileList) throws IOException {
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            for (int i = 0; i < files.length; i++) {
                if (files[i].isDirectory()) {
                    getFileList(files[i], fileList);
                } else {
                    fileList.add(files[i].getPath());
                }
            }
        }
    }

    public void generateJar() throws IOException {
        System.out.println("*** --> 开始生成jar包...");
        String targetDirPath = targetPath.substring(0, targetPath.lastIndexOf("/"));
        File targetDir = new File(targetDirPath);
        if (!targetDir.exists()) {
            targetDir.mkdirs();
        }

        Manifest manifest = new Manifest();
        manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
        for (Map.Entry<Object, Object> attribute : attributes.entrySet()) {
            manifest.getMainAttributes().put(attribute.getKey(), attribute.getValue());
        }


        JarOutputStream target = new JarOutputStream(new FileOutputStream(targetPath), manifest);
        writeClassFile(new File(jarClassPath), target);
        target.close();
        System.out.println("*** --> jar包生成完毕。");
    }

    private void writeClassFile(File source, JarOutputStream target) throws IOException {
        BufferedInputStream in = null;
        try {
            if (source.isDirectory()) {
                String name = source.getPath().replace("\\", "/");
                if (!name.isEmpty()) {
                    if (!name.endsWith("/")) {
                        name += "/";
                    }
                    name = name.substring(jarClassPath.length());
                    JarEntry entry = new JarEntry(name);
                    entry.setTime(source.lastModified());
                    target.putNextEntry(entry);
                    target.closeEntry();
                }
                for (File nestedFile : source.listFiles())
                    writeClassFile(nestedFile, target);
                return;
            }

            String middleName = source.getPath().replace("\\", "/").substring(javaClassPath.length());
            JarEntry entry = new JarEntry(middleName);
            entry.setTime(source.lastModified());
            target.putNextEntry(entry);
            in = new BufferedInputStream(new FileInputStream(source));

            byte[] buffer = new byte[1024];
            while (true) {
                int count = in.read(buffer);
                if (count == -1)
                    break;
                target.write(buffer, 0, count);
            }
            target.closeEntry();
        } finally {
            if (in != null)
                in.close();
        }
    }

    public static void main(String[] args) throws IOException, InterruptedException {
        String javaSourcePath = "/Users/yangguo/work/code/demo/src/main/java/info/yangguo/demo/attach_api/test4";
        String javaClassPath = "/Users/yangguo/Downloads/demo/classes/info/yangguo/demo/attach_api/test4";
        String jarClassPath = "/Users/yangguo/Downloads/demo/classes";
        String targetPath = "/Users/yangguo/Downloads/demo/target/libs/demo-1.0-SNAPSHOT-fat.jar";

        HashMap<Object, Object> attributes = new HashMap<>();
        Attributes.Name agentClass = new Attributes.Name("Agent-Class");
        attributes.put(agentClass, "info.yangguo.demo.attch_api.test4.Agent");
        Attributes.Name canRedineClasses = new Attributes.Name("Can-Redefine-Classes");
        attributes.put(canRedineClasses, "true");
        Attributes.Name canRetransformClasses = new Attributes.Name("Can-Retransform-Classes");
        attributes.put(canRetransformClasses, "true");

        JavacCompileAndJar javacCompileAndJar = new JavacCompileAndJar(javaSourcePath, javaClassPath, jarClassPath, targetPath, attributes);
        javacCompileAndJar.complier();
        javacCompileAndJar.generateJar();
    }

}

6.最后便是Instrumentation的的入口类,将Agentattach到前面的测试代码中去。代码中我将机器上所有的Java进程给遍历一遍,选择自己感兴趣的进程(TestExample)attach

package info.yangguo.demo.attch_api.test4;

/**
 * Created by IntelliJ IDEA
 * User:杨果
 * Date:15/6/29
 * Time:下午10:06
 * <p/>
 * Description:
 */

import com.sun.tools.attach.VirtualMachine;
import com.sun.tools.attach.VirtualMachineDescriptor;

import java.util.List;

public class AttachMain extends Thread {
    private final String jar;

    AttachMain(String attachJar) {
        jar = attachJar;
    }

    public void run() {
        VirtualMachine vm = null;
        List<VirtualMachineDescriptor> listAfter = null;
        try {
            int count = 0;
            while (true) {
                listAfter = VirtualMachine.list();
                for (VirtualMachineDescriptor vmd : listAfter) {
                    if (vmd.displayName().contains("TestExample")) {
                        // 如果 VM 有增加,我们就认为是被监控的 VM 启动了
                        // 这时,我们开始监控这个 VM
                        vm = VirtualMachine.attach(vmd);
                        break;
                    }
                }
                Thread.sleep(5000);
                count++;
                if (null != vm || count >= 10) {
                    break;
                }
            }
            vm.loadAgent(jar);
            vm.detach();
        } catch (Exception e) {
        }
    }

    public static void main(String[] args) throws InterruptedException {
        new AttachMain("/Users/yangguo/Downloads/demo/target/libs/demo-1.0-SNAPSHOT-fat.jar").start();
        Thread.sleep(10000000);
    }
}
分享到:
评论

相关推荐

    java agent注册为windows服务

    wrapper.java.additional.1=-javaagent:path/to/your/agent.jar ``` 6. **启动服务**:现在,当JSW启动Java应用程序时,它会先加载我们的Java Agent,然后由Agent完成服务注册。之后,你可以通过Windows服务管理...

    java JAVA_OPTS javaagent

    标题中的"java JAVA_OPTS javaagent"涉及到Java应用程序的启动参数设置,特别是`JAVA_OPTS`环境变量和`javaagent`选项,这两个是Java虚拟机(JVM)启动时的关键配置项。`JAVA_OPTS`通常用于传递额外的JVM参数,如...

    javaagent+javassist

    Javaagent和javassist是Java开发中的两个重要工具,它们在软件开发中有着广泛的应用,特别是在动态代理、代码增强和字节码操作等领域。本篇将详细介绍这两个技术,并结合实际示例进行解析。 首先,让我们来了解`...

    javaagent使用指南-rickiyang1

    Javaagent 是一种强大的工具,它允许开发者在 Java 程序运行时动态地修改类的行为。这个特性在很多场景下非常有用,比如性能监控、代码覆盖率分析、动态代理等。在本文中,我们将深入探讨 Javaagent 的使用以及其...

    java项目实现热更源码-javaagent

    2. **指定Agent参数**:在Java应用启动时,通过`-javaagent`参数指定Agent的路径和可选参数。例如:`-javaagent:/path/to/your/agent.jar=param1=value1,param2=value2`。 3. **操作类字节码**:Java Agent接收到...

    java 代理例子 -javaagent,premain方式实现

    在这个包中,`javaagent`接口是关键,它定义了一个`premain`方法,该方法会在目标应用程序启动之前被调用,允许我们在程序加载时进行干预。 `premain`方法的签名如下: ```java public static void premain(String ...

    JADE(Java Agent DEvelopment Framework):一个Agent,多Agent系统的开发框架

    在此和大家分享JADE(全称Java Agent DEvelopment Framework)一个很酷的在Java上进行Agent开发的框架。你可以用这个框架很轻易的搭建一个MAS(多智能体系统),在这些系统中,各个Agent是自治的,而且可以互相通信...

    java-agent演示项目,修改Java源代码_java-agent-demo.zip

    java-agent演示项目,修改Java源代码_java-agent-demo

    基于Bytebuddy的Java Agent AOP框架.zip

    Java Agent是一种在JVM级别对程序进行动态增强的技术,它允许我们无需修改源代码或重新编译就可以在运行时对类进行拦截、修改或者增强。ByteBuddy是Java的一个库,用于在运行时创建和修改Java类和接口,它是创建Java...

    jmx_prometheus_javaagent-0.13.0.jar

    https://pan.baidu.com/s/15TPev6CpXCRIPvXCIreMzA sbnh

    JavaAgent:Javassist 与 Asm JavaAgent 字节码动态编程项目

    在这个“JavaAgent: Javassist与Asm JavaAgent字节码动态编程项目”中,我们将深入探讨如何利用Javassist和ASM这两个库来实现JavaAgent。 首先,Javassist是一个开源的Java字节码操作框架,它使得开发者可以在运行...

    Java Agent源码(带Jar包)

    在源码中,我们可能会看到如何注册和使用Java Agent的示例,这通常涉及到`-javaagent`命令行参数的使用,以及`Premain-Class`和`Agent-Class` manifest条目的配置。同时,源码可能包含如何利用Instrumentation API来...

    idea maven 搭建java agent项目,手把手教你实现方法耗时统计的java agent.zip

    使用`-javaagent`参数指定你的Agent jar路径,以及`premain`方法的参数,如果有的话。 7. **收集和展示数据**:在Agent中,你需要实现数据的收集和展示。这可能包括将统计信息记录到日志文件,或者通过Socket发送到...

    apache-skywalking-java-agent-8.9.0

    在这个"apache-skywalking-java-agent-8.9.0"版本中,我们将探讨这个Java代理如何监控JVM服务,并深入理解其相关知识点。 1. **SkyWalking Java Agent**:SkyWalking的Java代理是一个自动探针,可以无侵入地附加到...

    基于Java Agent的动态字节码修改系统.zip

    基于Java Agent的动态字节码修改系统 项目简介 本项目是一个基于Java Agent技术的动态字节码修改系统,旨在通过Java Agent在JVM加载类文件时对特定的类进行字节码修改。系统支持通过系统默认配置和数据库配置两种...

    Java Agent实现系统数据采集

    Java Agent是一种强大的技术,它允许我们在Java应用程序运行时对其进行拦截和增强,而无需修改源代码或重新部署。这种技术在系统监控、日志记录、性能分析、安全审计等多个领域都有广泛应用。本文将深入探讨如何使用...

    移动机器人Java Agent控制系统设计 (1).pdf

    《移动机器人Java Agent控制系统设计》一文探讨了如何利用Java技术构建一个针对移动机器人的智能控制系统。文章的核心是基于Java的Agent系统,它利用JADE(Java Agent Development Framework)这一广泛应用的Java ...

    javaAgent实现补丁不重出功能,通过attach实现程序运行时加载,同时通过shell脚本部署到容器内

    JavaAgent是一种强大的技术,它允许在Java应用程序运行时对其进行拦截和修改,而无需停止或重新启动应用。在本文中,我们将深入探讨如何利用JavaAgent来实现补丁的动态加载,以及如何通过shell脚本将其部署到容器...

    Minecraft coremod Java Agent旨在修补在许多不同mod中发现的严重安全漏洞.zip

    《Minecraft核心模组Java Agent:修复安全漏洞的利器》 在广受欢迎的沙盒游戏Minecraft中,玩家可以通过各种模组(Mods)扩展游戏功能,丰富游戏体验。然而,这些模组的开放性同时也为游戏带来了安全隐患。为了应对...

    java-agent学习1

    - 为了使 Java Agent 生效,需要在启动参数中指定 `-javaagent:E:\java安全\java内存马\agent-example\agent-demo\target\java-agent.jar` 2. **实现 `Test` 类** - `Test` 类中包含一个私有变量 `hello` 和一个 ...

Global site tag (gtag.js) - Google Analytics