动态跟踪 java 运行程序,通过 hotswap 技术,动态将跟踪的字节码输入到运行类,对运行代码侵入小。
instrument + asm
1.对一个周期性读取网络操作的程序,增加监控程序耗时程序
被监控的程序
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.URL; import java.net.URLConnection; /** * @version 1.0 * @author: mousycoder * @date: 2019-07-22 15:49 */ public class HoldNetTask implements Runnable { public static void main(String[] args) { new Thread(new HoldNetTask()).start(); } public void visitWeb(String strUrl) { URL url = null; URLConnection urlConnection = null; InputStream is = null; try { url = new URL(strUrl); urlConnection = url.openConnection(); is = urlConnection.getInputStream(); BufferedReader buffer = new BufferedReader(new InputStreamReader(is)); StringBuffer bs = new StringBuffer(); String l = null; while ((l = buffer.readLine()) != null) { bs.append(l).append("/r/n"); } } catch (Exception e) { e.printStackTrace(); } finally { if (is != null) { try { is.close(); } catch (IOException e) { e.printStackTrace(); } } } } @Override public void run() { while (true) { visitWeb("http://www.sina.com.cn"); } } }
监控程序
import com.sun.btrace.annotations.*; import static com.sun.btrace.BTraceUtils.*; /** * @version 1.0 * @author: mousycoder * @date: 2019-07-22 15:58 */ @BTrace public class PrintTimes { @TLS private static long startTime = 0; @OnMethod(clazz = "/.+/",method = "/visitWeb/") // visitWeb方法开始的执行执行 public static void startMethod(){ startTime = timeMillis(); } @OnMethod(clazz = "/.+/",method = "/visitWeb/",location = @Location(Kind.RETURN)) //visitWeb方法结束的时候执行 public static void endMethod(){ print(strcat(strcat(name(probeClass()),"."),probeMethod())); print("["); print(strcat("Time taken: " , str(timeMillis() - startTime))); println("]"); } }
编写监控程序的时候,需要下载 btrace 客户端,maven 中配置该路径(中央仓库没有对应版本)
<!-- https://mvnrepository.com/artifact/io.btrace/btrace-maven --> <dependency> <groupId>com.sun.tools.btrace</groupId> <artifactId>btrace-agent</artifactId> <version>1.3.11.3</version> <scope>system</scope> <systemPath>/Users/mousycoder/My/software/btrace-bin-1.3.11.3/build/btrace-agent.jar</systemPath> </dependency> <dependency> <groupId>com.sun.tools.btrace</groupId> <artifactId>btrace-boot</artifactId> <scope>system</scope> <systemPath>/Users/mousycoder/My/software/btrace-bin-1.3.11.3/build/btrace-boot.jar</systemPath> </dependency> <dependency> <groupId>com.sun.tools.btrace</groupId> <artifactId>btrace-client</artifactId> <scope>system</scope> <systemPath>/Users/mousycoder/My/software/btrace-bin-1.3.11.3/build/btrace-client.jar</systemPath> </dependency>
先启动被监控程序 找到 pid 然后
btrace -v 16352 PrintTimes.java
即可开启监控程序
输出
DEBUG: received com.sun.btrace.comm.MessageCommand@2b515b5c HoldNetTask.visitWebDEBUG: received com.sun.btrace.comm.MessageCommand@31ba0af0 [DEBUG: received com.sun.btrace.comm.MessageCommand@752ccee0 Time taken: 20DEBUG: received com.sun.btrace.comm.MessageCommand@28ec9c23
打印出了visitWeb执行方法的耗时
2.输出被监控程序的参数
import com.sun.btrace.AnyType; import com.sun.btrace.annotations.BTrace; import com.sun.btrace.annotations.OnMethod; import com.sun.btrace.annotations.ProbeClassName; import com.sun.btrace.annotations.ProbeMethodName; import static com.sun.btrace.BTraceUtils.*; /** * @version 1.0 * @author: mousycoder * @date: 2019-07-22 17:05 */ @BTrace public class PrintTimes2 { @OnMethod(clazz = "/.*HoldNetTask/",method = "/visitWeb/") public static void anyWriteFile(@ProbeClassName String pcn, @ProbeMethodName String pmn, AnyType[] args){ print(pcn); print("."); print(pmn); printArray(args); } }
输出
DEBUG: received com.sun.btrace.comm.MessageCommand@6f96c77 HoldNetTaskDEBUG: received com.sun.btrace.comm.MessageCommand@be64738 .DEBUG: received com.sun.btrace.comm.MessageCommand@3ba9ad43 visitWebDEBUG: received com.sun.btrace.comm.MessageCommand@49d904ec [http://www.sina.com.cn, ]
可见已经输出了参数 http://www.sina.com.cn
3.输出被监控程序 27 行信息
import com.sun.btrace.AnyType; import com.sun.btrace.BTraceUtils; import com.sun.btrace.annotations.*; import static com.sun.btrace.BTraceUtils.print; import static com.sun.btrace.BTraceUtils.printArray; /** * @version 1.0 * @author: mousycoder * @date: 2019-07-22 17:05 */ @BTrace public class AllLines { @OnMethod(clazz = "/.*HoldNetTask/",location = @Location(value = Kind.LINE,line = 27)) public static void online(@ProbeClassName String pcn, @ProbeMethodName String pmn, int line){ print(BTraceUtils.Strings.strcat(pcn,".")); print(BTraceUtils.Strings.strcat(pmn,":")); print(line); } }
输出
27DEBUG: received com.sun.btrace.comm.MessageCommand@39529185 HoldNetTask.DEBUG: received com.sun.btrace.comm.MessageCommand@72f926e6 visitWeb:DEBUG: received com.sun.btrace.comm.MessageCommand@3daa422a 27DEBUG: received com.sun.btrace.comm.MessageCommand@31c88ec8 HoldNetTask.DEBUG: received com.sun.btrace.comm.MessageCommand@1cbbffcd
4.新增定时任务(每秒输出虚拟机启动时间+每 3 秒导出线程堆栈)
import com.sun.btrace.BTraceUtils; import com.sun.btrace.annotations.BTrace; import com.sun.btrace.annotations.OnTimer; import static com.sun.btrace.BTraceUtils.jstackAll; import static com.sun.btrace.BTraceUtils.println; /** * @version 1.0 * @author: mousycoder * @date: 2019-07-22 17:05 */ @BTrace public class Timers { @OnTimer(1000) //每秒运行 public static void getUpTime(){ println(BTraceUtils.Strings.strcat("1000 msec:", BTraceUtils.Strings.str(BTraceUtils.Sys.VM.vmUptime()))); //虚拟机启动时间 } @OnTimer(3000) //每 3 秒运行 public static void getStack(){ jstackAll(); //导出线程堆栈 } }
输出
com.sun.btrace.BTraceRuntime$19.run(BTraceRuntime.java:2387) java.util.TimerThread.mainLoop(Timer.java:555) java.util.TimerThread.run(Timer.java:505) Thread[RMI Scheduler(0),9,system] sun.misc.Unsafe.park(Native Method) java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215) java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2078) java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1093) java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:809) java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067) java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127) java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) java.lang.Thread.run(Thread.java:744) DEBUG: received com.sun.btrace.comm.MessageCommand@3aeaafa6 1000 msec:1543007
5.打印实例属性
import com.sun.btrace.annotations.*; import static com.sun.btrace.BTraceUtils.*; /** * @version 1.0 * @author: mousycoder * @date: 2019-07-22 17:05 */ @BTrace public class PrintField { @OnMethod(clazz = "/.*URL/",method = "/.*openConnection/",location = @Location(value = Kind.ENTRY)) public static void visitWebEntry(@Self Object self){ println(strcat(strcat(name(probeClass()),"."),probeMethod())); println(self); println(get(field(classOf(self),"protocolPathProp"))); //只能取 static 变量 println(get(field(classOf(self),"host"),self)); //获取实例变量 println("==========="); } }
监控 URL 对象,在调用 openConnectio()方法的时候,获得当前实例 self ,然后获取属性
输出
DEBUG: received com.sun.btrace.comm.MessageCommand@3ba987b8 java.net.URL.openConnection DEBUG: received com.sun.btrace.comm.MessageCommand@3f191845 http://www.sina.com.cn
6.谁调用了 GC
import com.sun.btrace.annotations.*; import static com.sun.btrace.BTraceUtils.*; /** * @version 1.0 * @author: mousycoder * @date: 2019-07-22 17:05 */ @BTrace public class OnSystemGC { @OnMethod(clazz = "java.lang.System",method = "gc") public static void onSystemGC(){ println("enterd System.gc()"); jstack(); } }
7.更多例子可以查看 btrace 客户端目录下的 samples 文件夹