粗略的点开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);
}
}