Btrace
Btrace用於調試正在運行的系統,並且在調試時不會暫停系統。特別適用於跟蹤線上問題。你可以實時監控一個系統中任何一個方法的調用,你可以知道這些方法的參數、返回值是什麼,還可以知道方法調用消耗了多少時間。
Btrace不需要安裝,只要下載一個包,解壓即可。
Btrace用法爲bin/btrace <pid> <trace-script>
。其中pid
是正在運行的java進程,trace-script
是跟蹤腳本,它其實就是一段java代碼。
Hello World
首先我們模擬一個正在運行的程序,它僅有一個循環。
package com.caipeichao;
public class NullApp {
public static void main(String[] argv) {
new NullApp().run();
}
public void run() {
for (int i = 0; i < 100000; i++) {
sleep(1000);
new MyObj().life(i);
}
}
private static class MyObj {
public void life(int n) {
System.out.println(n);
}
}
private void sleep(int n) {
try {
Thread.sleep(n);
} catch (InterruptedException e) {
}
}
}
然後開啓這個程序: java com.caipeichao.NullApp
通過jps命令得到這個程序的PID,這裏爲13348。
> jps
3034 RemoteMavenServer
2902 Main
15147 Jps
13348 NullApp
準備工作做完了,現在編寫最重要的跟蹤腳本。
import static com.sun.btrace.BTraceUtils.*;
import com.sun.btrace.annotations.*;
@BTrace
public class HelloBtrace {
@OnMethod(clazz="com.caipeichao.NullApp",
method="sleep",
location=@Location(Kind.RETURN))
public static void onSleep() {
println("Hello world");
}
}
運行btrace,得到如下輸出。
> btrace 13348 HelloBtrace.java
Hello world
Hello world
Hello world
Hello world
Hello world
Hello world
常用註解
名稱 |
作用域 |
作用 |
---|
@BTrace |
類 |
聲明跟蹤腳本 |
@OnMethod(clazz,method,location) |
方法 |
當指定方法被調用時 |
@OnMethod(method="<init>") |
方法 |
當構造函數被調用時 |
@OnMethod(clazz="/java\\.io\\..*Input/")) |
方法 |
方法名稱正則匹配 |
@Location(kind) |
@OnMethod |
指定監控方法調用前還是調用後 |
@Location(value=Kind.NEWARRAY, clazz="char") |
@OnMethod |
監控新增數組 |
@Self |
參數 |
表示被監控的對象 |
@ProbeMethodName |
參數 |
被監控的方法名稱 |
@ProbeClassName |
參數 |
被監控的類名 |
@OnTimer(interval) |
方法 |
定時調用某個方法 |
@OnLowMemory(pool,threshold) |
方法 |
當內存不足時 |
@OnExit |
方法 |
當程序退出時 |
@OnProbe(namespace="java.net.socket",name="bind") |
方法 |
監控socket中的bind方法 |
常用方法
方法 |
作用 |
---|
println |
在本地控制檯輸出一行 |
print |
在本地控制檯輸出 |
printArray |
在本地控制檯輸出數組 |
jstack |
打印遠程方法的調用調用棧 |
jstackAll |
輸出所有線程的調用棧 |
exit |
退出跟蹤腳本 |
Strings.strcat |
連接字符串 |
Reflactive.name |
獲取類名 |
Threads.name |
線程名 |
Threads.currentThread |
當前線程 |
deadlocks |
打出死鎖線程 |
sizeof |
獲取對象的大小,比如List 對象就返回List.size() |
Sys.Env.property |
獲取系統變量 |
原理
BTrace利用了java.lang.instrument
包實現代碼注入。首先通過VirtualMachine.attach(pid)
連接遠程JVM,然後通過VirtualMachine.loadAgent("*.jar")
加載一個btrace的jar包。這個jar包最重要的代碼如下。
public static void premain(String args, Instrumentation inst) {
main(args, inst);
}
public static void agentmain(String args, Instrumentation inst) {
main(args, inst);
}
private static synchronized void main(final String args, final Instrumentation inst) {
...
inst.appendToBootstrapClassLoaderSearch(new JarFile(new File(path)));
...
inst.appendToSystemClassLoaderSearch(new JarFile(new File(path)));
...
startServer();
}
private static void startServer() {
...
while (true) {
try {
...
handleNewClient(client);
} catch (RuntimeException re) {
if (isDebug()) debugPrint(re);
} catch (IOException ioexp) {
if (isDebug()) debugPrint(ioexp);
}
}
}
private static void handleNewClient(final Client client) {
...
inst.addTransformer(client, true);
...
inst.retransformClasses(classes);
}
abstract class Client implements ClassFileTransformer, CommandListener {
static {
ClassFilter.class.getClass();
ClassReader.class.getClass();
ClassWriter.class.getClass();
...
}
private byte[] instrument(Class clazz, String cname, byte[] target) {
byte[] instrumentedCode;
try {
ClassWriter writer = InstrumentUtils.newClassWriter(target);
ClassReader reader = new ClassReader(target);
Instrumentor i = new Instrumentor(clazz, className, btraceCode, onMethods, writer);
...
}
}
一句話總結,btrace利用instrument工具修改JVM內存中的類字節碼,達到注入代碼的目的。