arthas与jvm-sandbox是阿里开源的JVM工具,都能够attach到一个正在运行的Java进程中,可以查看运行时的一些信息,并且可以通过对字节码的修改,来实现一些高级功能。
arthas和jvm-sandbox之所以能实现这些功能,主要依赖的有Java agent和ASM字节码修改。
-
java agent是Jdk1.5之后引入的技术,可以随Java进程一起启动,或者attach到一个已经运行的Java进程。 在attach到进程后,Java本身提供了一些类(Instrumentation), 可以获取到一些运行信息,并且也提供了一些方法,可以对字节码进行干预。
-
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.