arthas与jvm-sandbox

arthas与jvm-sandbox是阿里开源的JVM工具,都能够attach到一个正在运行的Java进程中,可以查看运行时的一些信息,并且可以通过对字节码的修改,来实现一些高级功能。 
arthas和jvm-sandbox之所以能实现这些功能,主要依赖的有Java agent和ASM字节码修改。
  1. java agent是Jdk1.5之后引入的技术,可以随Java进程一起启动,或者attach到一个已经运行的Java进程。 在attach到进程后,Java本身提供了一些类(Instrumentation), 可以获取到一些运行信息,并且也提供了一些方法,可以对字节码进行干预。
  2. ASM可以生成或者修改字节码, 配合Instrumentation的addTransformer()和retransformClasses()方法,可以完成对字节码的修改。 
 
为了便于对着两个技术的理解,我这里简化了一下,实现了一个demo。 
业务类:
package com.demo;

public class Service {
public void dosome() {
System.out.println(
"do some..."); } }
package com.demo;

import java.lang.management.ManagementFactory;
import java.util.Scanner;

/**
 * Hello world!
 */
public class App {

    public static void main(String[] args) {

        // 获取pid  供agent使用
        String name = ManagementFactory.getRuntimeMXBean().getName();
        String pid = name.split("@")[0];
        System.out.println("应用程序进程Id:" + pid);

        // 注意这里实例化,类加载一次, 之后没有再加载
        Service service = new Service();

        // 键盘上输入F值,模拟业务执行
        Scanner scanner = new Scanner(System.in);
        System.out.println("按 F 执行程序");

        while (true) {
            String cmd = scanner.nextLine();
            if ("f".equalsIgnoreCase(cmd)) {
                service.dosome();
            }
        }
    }
}

 

代理类:

package com.agent;

import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;

/**
 * Hello world!
 */
public class AgentLauncher {

    public static void premain(String featureString, Instrumentation inst) {
        System.out.println("pre main");
    }

    /**
     * 通过attach的方式
     */
    public static void agentmain(String featureString, Instrumentation inst) throws UnmodifiableClassException {
        System.out.println("agent!!! 增强业务方法");
        // 增强
        MyClassFileTransformer myClassFileTransformer = new MyClassFileTransformer();
        // 添加到Instrumentation
        inst.addTransformer(myClassFileTransformer, true);

        // 找到需要重新加载的类
        Class<?>[] loadeds = inst.getAllLoadedClasses();
        Class<?> targetClass = null;
        for (Class<?> loaded : loadeds) {
            if (loaded.getName().equals("com.demo.Service")) {
                targetClass = loaded;
            }
        }
        // 重新加载类, 保证动态的增强类生效
        inst.retransformClasses(targetClass);
        System.out.println("增强完毕,请重新执行业务方法");

    }


}

 

最后是attach

package com.attach;

import com.sun.tools.attach.VirtualMachine;

/**
 * Hello world!
 */
public class App {

    public static void main(String[] args) throws Exception {

        String targetJvmPid = "912"; // 业务进程ID
        if (args != null) {
            if (args.length > 0) {
                targetJvmPid = args[0];
            }
        }

        if (targetJvmPid != null) {
            VirtualMachine vmObj = null;
            try {
                vmObj = VirtualMachine.attach(targetJvmPid);
                if (vmObj != null) {
                    // 加载agent的jar包, 需要先在agent项目中执行 maven package命令生成
                    vmObj.loadAgent("D:\\jar\\agent-1.0-jar-with-dependencies.jar");
                }

            } finally {
                if (null != vmObj) {
                    vmObj.detach();
                }
            }
        }
    }
}

 

最后整体的运行效果如下:

 

第一个红框是业务类正常运行的结果

第二个红框执行attach的效果

第三个是重新运行业务代码的效果,可以看到方法在执行前后加了一个start和end.

完整的代码参考:https://github.com/zhaoyb/java-agent

posted @ 2021-09-15 16:31  秋夜  阅读(595)  评论(0编辑  收藏  举报