粗略的点开btrace的源码看了一下,实际上他只是封装了JDK自带的功能而已
1. attach client到java进程
VirtualMachine vm = null; if (debug) { debugPrint("attaching to " + pid); } vm = VirtualMachine.attach(pid); if (debug) { debugPrint("checking port availability: " + port); } Properties serverVmProps = vm.getSystemProperties(); int serverPort = Integer.parseInt(serverVmProps.getProperty("btrace.port", "-1")); if (serverPort != -1) { if (serverPort != port) { throw new IOException("Can not attach to PID " + pid + " on port " + port + ". There is already a BTrace server active on port " + serverPort + "!"); } } else { if (!isPortAvailable(port)) { throw new IOException("Port " + port + " unavailable."); } } vm.loadAgent(agentPath, agentArgs);
2. 修改字节码
获取 Instrumentation 接口的实例有两种方式:
当 JVM 以指示一个代理类的方式启动时,将传递给代理类的 premain 方法一个 Instrumentation 实例。
当 JVM 提供某种机制在 JVM 启动之后某一时刻启动代理时,将传递给代理代码的 agentmain 方法一个 Instrumentation 实例。
拿到Instrumentation实例后,就可以替换class字节码了
// 返回 JVM 当前加载的所有类的数组 for (Class c : inst.getAllLoadedClasses()) { if (c != null) { cc.get(c); if (inst.isModifiableClass(c) && isCandidate(c)) { debugPrint("candidate " + c + " added"); list.add(c); } } } list.trimToSize(); int size = list.size(); if (size > 0) { Class[] classes = new Class[size]; list.toArray(classes); startRetransformClasses(size); if (isDebug()) { for(Class c : classes) { try { // 如果重转换的方法有活动的堆栈帧,那么这些活动的帧将继续运行原方法的字节码。重转换的方法将用于新的调用 // 此方法不会引起任何初始化操作,JVM 惯例语义下发生的初始化除外。换句话说,重定义一个类不会引起其初始化方法的运行。静态变量的值将与调用之前的值一样。 inst.retransformClasses(c); } catch (VerifyError e) { debugPrint("verification error: " + c.getName()); } } } else { inst.retransformClasses(classes); } }