写那么多年Java,还不知道啥是Java agent的必须看一下!
簡(jiǎn)介:?作者信息:張帥,花名洵澈,負(fù)責(zé)物流表達(dá)和履約相關(guān)研發(fā)工作。熱衷于中間件相關(guān)技術(shù)。
引言
在本篇文章中,我會(huì)通過(guò)幾個(gè)簡(jiǎn)單的程序來(lái)說(shuō)明agent的使用,最后在實(shí)戰(zhàn)替換我會(huì)通過(guò)asm字節(jié)碼框架來(lái)實(shí)現(xiàn)一個(gè)小工具,用于在程序運(yùn)行中采集指定方法的參數(shù)和返回值。有關(guān)asm字節(jié)碼的內(nèi)容不是本文的重點(diǎn),不會(huì)過(guò)多的分解,不明白的同學(xué)可以自己的Google下。
簡(jiǎn)介
Java代理提供了一種在加載字節(jié)碼時(shí),對(duì)字節(jié)碼進(jìn)行修改的方式。他共有兩種方式執(zhí)行,一種是在main方法執(zhí)行之前,通過(guò)premain來(lái)實(shí)現(xiàn),另一種是在程序運(yùn)行中,通過(guò)attach api來(lái)實(shí)現(xiàn)。
在介紹agent之前,先給大家簡(jiǎn)單說(shuō)下一個(gè)Instrumentation。它是JDK1.5提供的API,用于攔截類(lèi)加載事件,通過(guò)字節(jié)碼進(jìn)行修改,它的主要方法如下:
public interface Instrumentation {//注冊(cè)一個(gè)轉(zhuǎn)換器,類(lèi)加載事件會(huì)被注冊(cè)的轉(zhuǎn)換器所攔截void addTransformer(ClassFileTransformer transformer, boolean canRetransform);//重新觸發(fā)類(lèi)加載void retransformClasses(Class<?>... classes) throws UnmodifiableClassException;//直接替換類(lèi)的定義void redefineClasses(ClassDefinition... definitions) throws ClassNotFoundException, UnmodifiableClassException; }主要
premain是在main方法之前運(yùn)行的方法,也是最常見(jiàn)的agent方式。運(yùn)行時(shí)需要將agent程序打包成jar包,并在啟動(dòng)時(shí)添加命令來(lái)執(zhí)行,如下文所示:
java -javaagent:agent.jar=xunche HelloWorld
premain共提供以下2種重載方法,Jvm啟動(dòng)時(shí)會(huì)先嘗試使用第一種方法,若沒(méi)有會(huì)使用第二種方法:
public static void premain(String agentArgs, Instrumentation inst); public static void premain(String agentArgs);一個(gè)簡(jiǎn)單的例子
下面我們通過(guò)一個(gè)程序來(lái)簡(jiǎn)單說(shuō)明下premain的使用,首先我們準(zhǔn)備下測(cè)試代碼,測(cè)試代碼比較簡(jiǎn)單,運(yùn)行main方法并輸出hello world。
package org.xunche.app; public class HelloWorld {public static void main(String[] args) {System.out.println("Hello World");} }接下來(lái)我們看下agent的代碼,運(yùn)行premain方法并輸出我們預(yù)期的參數(shù)。
package org.xunche.agent; public class HelloAgent {public static void premain(String args) {System.out.println("Hello Agent: " + args);} }為了能夠使agent能夠運(yùn)行,我們需要將META-INF / MANIFEST.MF文件中的Premain- Class為我們編寫(xiě)的agent路徑,然后通過(guò)以下方式將其打包成jar包,當(dāng)然你也可以使用idea直接替換jar包。
echo 'Premain-Class: org.xunche.agent.HelloAgent' > manifest.mf javac org/xunche/agent/HelloAgent.java javac org/xunche/app/HelloWorld.java jar cvmf manifest.mf hello-agent.jar org/接下來(lái),我們編譯下并運(yùn)行下測(cè)試代碼,這里為了測(cè)試簡(jiǎn)單,我將編譯后的類(lèi)和agent的jar包放在同級(jí)目錄下
java -javaagent:hello-agent.jar=xunche org/xunche/app/HelloWorld可以看到輸出結(jié)果如下,agent中的premain方法有延續(xù)main方法執(zhí)行
Hello Agent: xunche Hello World稍微復(fù)雜點(diǎn)的例子
通過(guò)上面的例子,是否對(duì)agent有個(gè)簡(jiǎn)單的了解呢?
下面我們來(lái)看一個(gè)稍微復(fù)雜點(diǎn),我們通過(guò)agent來(lái)實(shí)現(xiàn)一個(gè)方法監(jiān)控的功能。思路大致是這樣的,若是非jdk的方法,我們通過(guò)asm在方法的執(zhí)行入口和執(zhí)行出口處,植入幾行記錄最佳的代碼,當(dāng)方法結(jié)束后,通過(guò)合并來(lái)獲取方法的耗時(shí)。
首先還是看下測(cè)試代碼,邏輯很簡(jiǎn)單,main方法執(zhí)行時(shí)調(diào)用say Hi方法,輸出hi,xunche,并隨機(jī)睡眠中斷。
package org.xunche.app; public class HelloXunChe {public static void main(String[] args) throws InterruptedException {HelloXunChe helloXunChe = new HelloXunChe();helloXunChe.sayHi();}public void sayHi() throws InterruptedException {System.out.println("hi, xunche");sleep();}public void sleep() throws InterruptedException {Thread.sleep((long) (Math.random() * 200));} }接下來(lái)我們替換asm來(lái)植入我們自己的代碼,在jvm加載類(lèi)的時(shí)候,為類(lèi)的每個(gè)方法加上統(tǒng)計(jì)方法調(diào)用耗時(shí)的代碼,代碼如下,這里的asm我使用了jdk自帶的,當(dāng)然你也可以使用官方的asm類(lèi)庫(kù)。
package org.xunche.agent; import jdk.internal.org.objectweb.asm.*; import jdk.internal.org.objectweb.asm.commons.AdviceAdapter; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.Instrumentation; import java.security.ProtectionDomain; public class TimeAgent {public static void premain(String args, Instrumentation instrumentation) {instrumentation.addTransformer(new TimeClassFileTransformer());}private static class TimeClassFileTransformer implements ClassFileTransformer {@Overridepublic byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) {if (className.startsWith("java") || className.startsWith("jdk") || className.startsWith("javax") || className.startsWith("sun") || className.startsWith("com/sun")|| className.startsWith("org/xunche/agent")) {//return null或者執(zhí)行異常會(huì)執(zhí)行原來(lái)的字節(jié)碼return null;}System.out.println("loaded class: " + className);ClassReader reader = new ClassReader(classfileBuffer);ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);reader.accept(new TimeClassVisitor(writer), ClassReader.EXPAND_FRAMES);return writer.toByteArray();}}public static class TimeClassVisitor extends ClassVisitor {public TimeClassVisitor(ClassVisitor classVisitor) {super(Opcodes.ASM5, classVisitor);}@Overridepublic MethodVisitor visitMethod(int methodAccess, String methodName, String methodDesc, String signature, String[] exceptions) {MethodVisitor methodVisitor = cv.visitMethod(methodAccess, methodName, methodDesc, signature, exceptions);return new TimeAdviceAdapter(Opcodes.ASM5, methodVisitor, methodAccess, methodName, methodDesc);}}public static class TimeAdviceAdapter extends AdviceAdapter {private String methodName;protected TimeAdviceAdapter(int api, MethodVisitor methodVisitor, int methodAccess, String methodName, String methodDesc) {super(api, methodVisitor, methodAccess, methodName, methodDesc);this.methodName = methodName;}@Overrideprotected void onMethodEnter() {//在方法入口處植入if ("<init>".equals(methodName)|| "<clinit>".equals(methodName)) {return;}mv.visitTypeInsn(NEW, "java/lang/StringBuilder");mv.visitInsn(DUP);mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);mv.visitVarInsn(ALOAD, 0);mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Object", "getClass", "()Ljava/lang/Class;", false);mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "getName", "()Ljava/lang/String;", false);mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);mv.visitLdcInsn(".");mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);mv.visitLdcInsn(methodName);mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);mv.visitMethodInsn(INVOKESTATIC, "org/xunche/agent/TimeHolder", "start", "(Ljava/lang/String;)V", false);}@Overrideprotected void onMethodExit(int i) {//在方法出口植入if ("<init>".equals(methodName) || "<clinit>".equals(methodName)) {return;}mv.visitTypeInsn(NEW, "java/lang/StringBuilder");mv.visitInsn(DUP);mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);mv.visitVarInsn(ALOAD, 0);mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Object", "getClass", "()Ljava/lang/Class;", false);mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "getName", "()Ljava/lang/String;", false);mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);mv.visitLdcInsn(".");mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);mv.visitLdcInsn(methodName);mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);mv.visitVarInsn(ASTORE, 1);mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");mv.visitTypeInsn(NEW, "java/lang/StringBuilder");mv.visitInsn(DUP);mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);mv.visitVarInsn(ALOAD, 1);mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);mv.visitLdcInsn(": ");mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);mv.visitVarInsn(ALOAD, 1);mv.visitMethodInsn(INVOKESTATIC, "org/xunche/agent/TimeHolder", "cost", "(Ljava/lang/String;)J", false);mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(J)Ljava/lang/StringBuilder;", false);mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);}} }的上述代碼略長(zhǎng),ASM的部分可以略過(guò)。我們通過(guò)instrumentation.addTransformer注冊(cè)一個(gè)轉(zhuǎn)換器,轉(zhuǎn)換器重寫(xiě)了變換方法,方法入?yún)⒅械腸lassfileBuffer表示的是原始的字節(jié)碼,方法返回值表示的是真正要進(jìn)行加載的字節(jié)碼。
onMethodEnter方法中的代碼含義是調(diào)用TimeHolder的start方法并初始化當(dāng)前的方法名。
onMethodExit方法中的代碼含義是調(diào)用TimeHolder的成本方法并合并當(dāng)前的方法名,并打印成本方法的返回值。
下面來(lái)看下TimeHolder的代碼:
package org.xunche.agent; import java.util.HashMap; import java.util.Map; public class TimeHolder {private static Map<String, Long> timeCache = new HashMap<>();public static void start(String method) {timeCache.put(method, System.currentTimeMillis());}public static long cost(String method) {return System.currentTimeMillis() - timeCache.get(method);} }至此之后,agent的代碼編寫(xiě)完成,有關(guān)asm的部分不是本章的重點(diǎn),日后再單獨(dú)推出發(fā)表有關(guān)asm的文章。后的代碼是怎樣的??梢钥吹?#xff0c;與最開(kāi)始的測(cè)試代碼排序,每個(gè)方法都加入了我們統(tǒng)計(jì)方法耗時(shí)的代碼。
package org.xunche.app; import org.xunche.agent.TimeHolder; public class HelloXunChe {public HelloXunChe() {}public static void main(String[] args) throws InterruptedException {TimeHolder.start(args.getClass().getName() + "." + "main");HelloXunChe helloXunChe = new HelloXunChe();helloXunChe.sayHi();HelloXunChe helloXunChe = args.getClass().getName() + "." + "main";System.out.println(helloXunChe + ": " + TimeHolder.cost(helloXunChe));}public void sayHi() throws InterruptedException {TimeHolder.start(this.getClass().getName() + "." + "sayHi");System.out.println("hi, xunche");this.sleep();String var1 = this.getClass().getName() + "." + "sayHi";System.out.println(var1 + ": " + TimeHolder.cost(var1));}public void sleep() throws InterruptedException {TimeHolder.start(this.getClass().getName() + "." + "sleep");Thread.sleep((long)(Math.random() * 200.0D));String var1 = this.getClass().getName() + "." + "sleep";System.out.println(var1 + ": " + TimeHolder.cost(var1));} }代理基礎(chǔ)
上面的premain是通過(guò)agetn在應(yīng)用啟動(dòng)前,對(duì)字節(jié)碼進(jìn)行修改,來(lái)實(shí)現(xiàn)我們想要的功能。實(shí)際上jdk提供了attach api,通過(guò)這個(gè)api,我們可以訪問(wèn)已經(jīng)啟動(dòng)的Java進(jìn)程。并通過(guò)agentmain方法來(lái)攔截類(lèi)加載。下面我們來(lái)通過(guò)實(shí)戰(zhàn)來(lái)具體說(shuō)明下agentmain。
實(shí)戰(zhàn)
本次實(shí)戰(zhàn)的目標(biāo)是實(shí)現(xiàn)一個(gè)小工具,其目標(biāo)是能遠(yuǎn)程采集已經(jīng)在運(yùn)行中的Java進(jìn)程的方法調(diào)用信息。聽(tīng)起來(lái)像不像BTrace,實(shí)際上 BTrace也是 這么實(shí)現(xiàn)的。只不過(guò)因?yàn)闀r(shí)間關(guān)系,本次的實(shí)戰(zhàn)代碼寫(xiě)的比較簡(jiǎn)陋,大家不必關(guān)注細(xì)節(jié),看下實(shí)現(xiàn)的思路就好。
具體的實(shí)現(xiàn)思路如下:
agent對(duì)指定類(lèi)的方法進(jìn)行字節(jié)碼的修改,采集方法的入?yún)⒑头祷刂?。并通過(guò)socket將請(qǐng)求和返回發(fā)送到服務(wù)端
服務(wù)端通過(guò)attach api訪問(wèn)運(yùn)行中的Java進(jìn)程,并加載agent,使agent程序能對(duì)目標(biāo)進(jìn)程實(shí)施
服務(wù)端加載agent時(shí)指定需要采集的類(lèi)和方法
服務(wù)端開(kāi)啟一個(gè)端口,接受目標(biāo)進(jìn)程的請(qǐng)求信息
老規(guī)矩,先看測(cè)試代碼,測(cè)試代碼很簡(jiǎn)單,每隔100ms運(yùn)行一次sayHi方法,并隨機(jī)隨身睡覺(jué)。
package org.xunche.app; public class HelloTraceAgent {public static void main(String[] args) throws InterruptedException {HelloTraceAgent helloTraceAgent = new HelloTraceAgent();while (true) {helloTraceAgent.sayHi("xunche");Thread.sleep(100);}}public String sayHi(String name) throws InterruptedException {sleep();String hi = "hi, " + name + ", " + System.currentTimeMillis();return hi;}public void sleep() throws InterruptedException {Thread.sleep((long) (Math.random() * 200));} }接下看agent代碼,思路同等監(jiān)控方法耗時(shí)差不多,在方法出口處,通過(guò)asm植入采集方法入?yún)⒑头祷刂档拇a,并通過(guò)發(fā)件人將信息通過(guò)socket發(fā)送到服務(wù)端,代碼如下:
package org.xunche.agent; import jdk.internal.org.objectweb.asm.*; import jdk.internal.org.objectweb.asm.commons.AdviceAdapter; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.Instrumentation; import java.lang.instrument.UnmodifiableClassException; import java.security.ProtectionDomain; public class TraceAgent {public static void agentmain(String args, Instrumentation instrumentation) throws ClassNotFoundException, UnmodifiableClassException {if (args == null) {return;}int index = args.lastIndexOf(".");if (index != -1) {String className = args.substring(0, index);String methodName = args.substring(index + 1);//目標(biāo)代碼已經(jīng)加載,需要重新觸發(fā)加載流程,才會(huì)通過(guò)注冊(cè)的轉(zhuǎn)換器進(jìn)行轉(zhuǎn)換instrumentation.addTransformer(new TraceClassFileTransformer(className.replace(".", "/"), methodName), true);instrumentation.retransformClasses(Class.forName(className));}}public static class TraceClassFileTransformer implements ClassFileTransformer {private String traceClassName;private String traceMethodName;public TraceClassFileTransformer(String traceClassName, String traceMethodName) {this.traceClassName = traceClassName;this.traceMethodName = traceMethodName;}@Overridepublic byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) {//過(guò)濾掉Jdk、agent、非指定類(lèi)的方法if (className.startsWith("java") || className.startsWith("jdk") || className.startsWith("javax") || className.startsWith("sun")|| className.startsWith("com/sun") || className.startsWith("org/xunche/agent") || !className.equals(traceClassName)) {//return null會(huì)執(zhí)行原來(lái)的字節(jié)碼return null;}ClassReader reader = new ClassReader(classfileBuffer);ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);reader.accept(new TraceVisitor(className, traceMethodName, writer), ClassReader.EXPAND_FRAMES);return writer.toByteArray();}}public static class TraceVisitor extends ClassVisitor {private String className;private String traceMethodName;public TraceVisitor(String className, String traceMethodName, ClassVisitor classVisitor) {super(Opcodes.ASM5, classVisitor);this.className = className;this.traceMethodName = traceMethodName;}@Overridepublic MethodVisitor visitMethod(int methodAccess, String methodName, String methodDesc, String signature, String[] exceptions) {MethodVisitor methodVisitor = cv.visitMethod(methodAccess, methodName, methodDesc, signature, exceptions);if (traceMethodName.equals(methodName)) {return new TraceAdviceAdapter(className, methodVisitor, methodAccess, methodName, methodDesc);}return methodVisitor;}}private static class TraceAdviceAdapter extends AdviceAdapter {private final String className;private final String methodName;private final Type[] methodArgs;private final String[] parameterNames;private final int[] lvtSlotIndex;protected TraceAdviceAdapter(String className, MethodVisitor methodVisitor, int methodAccess, String methodName, String methodDesc) {super(Opcodes.ASM5, methodVisitor, methodAccess, methodName, methodDesc);this.className = className;this.methodName = methodName;this.methodArgs = Type.getArgumentTypes(methodDesc);this.parameterNames = new String[this.methodArgs.length];this.lvtSlotIndex = computeLvtSlotIndices(isStatic(methodAccess), this.methodArgs);}@Overridepublic void visitLocalVariable(String name, String description, String signature, Label start, Label end, int index) {for (int i = 0; i < this.lvtSlotIndex.length; ++i) {if (this.lvtSlotIndex[i] == index) {this.parameterNames[i] = name;}}}@Overrideprotected void onMethodExit(int opcode) {//排除構(gòu)造方法和靜態(tài)代碼塊if ("<init>".equals(methodName) || "<clinit>".equals(methodName)) {return;}if (opcode == RETURN) {push((Type) null);} else if (opcode == LRETURN || opcode == DRETURN) {dup2();box(Type.getReturnType(methodDesc));} else {dup();box(Type.getReturnType(methodDesc));}Type objectType = Type.getObjectType("java/lang/Object");push(lvtSlotIndex.length);newArray(objectType);for (int j = 0; j < lvtSlotIndex.length; j++) {int index = lvtSlotIndex[j];Type type = methodArgs[j];dup();push(j);mv.visitVarInsn(ALOAD, index);box(type);arrayStore(objectType);}visitLdcInsn(className.replace("/", "."));visitLdcInsn(methodName);mv.visitMethodInsn(INVOKESTATIC, "org/xunche/agent/Sender", "send", "(Ljava/lang/Object;[Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;)V", false);}private static int[] computeLvtSlotIndices(boolean isStatic, Type[] paramTypes) {int[] lvtIndex = new int[paramTypes.length];int nextIndex = isStatic ? 0 : 1;for (int i = 0; i < paramTypes.length; ++i) {lvtIndex[i] = nextIndex;if (isWideType(paramTypes[i])) {nextIndex += 2;} else {++nextIndex;}}return lvtIndex;}private static boolean isWideType(Type aType) {return aType == Type.LONG_TYPE || aType == Type.DOUBLE_TYPE;}private static boolean isStatic(int access) {return (access & 8) > 0;}} }SpringLocalVariableTableParameterNameNameDiscoverer,注意的同學(xué)可以自己研究下。接下來(lái)看下Sender中級(jí)代碼:
public class Sender {private static final int SERVER_PORT = 9876;public static void send(Object response, Object[] request, String className, String methodName) {Message message = new Message(response, request, className, methodName);try {Socket socket = new Socket("localhost", SERVER_PORT);socket.getOutputStream().write(message.toString().getBytes());socket.close();} catch (IOException e) {e.printStackTrace();}}private static class Message {private Object response;private Object[] request;private String className;private String methodName;public Message(Object response, Object[] request, String className, String methodName) {this.response = response;this.request = request;this.className = className;this.methodName = methodName;}@Overridepublic String toString() {return "Message{" +"response=" + response +", request=" + Arrays.toString(request) +", className='" + className + '\'' +", methodName='" + methodName + '\'' +'}';}} }Sender中的代碼不復(fù)雜,一看就懂,就不多說(shuō)了。下面我們來(lái)看下服務(wù)端的代碼,服務(wù)端要實(shí)現(xiàn)開(kāi)啟一個(gè)端口監(jiān)聽(tīng),接受請(qǐng)求信息,并使用attach api加載agent。
package org.xunche.app; import com.sun.tools.attach.AgentInitializationException; import com.sun.tools.attach.AgentLoadException; import com.sun.tools.attach.AttachNotSupportedException; import com.sun.tools.attach.VirtualMachine; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.ServerSocket; import java.net.Socket; public class TraceAgentMain {private static final int SERVER_PORT = 9876;public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException {new Server().start();//attach的進(jìn)程VirtualMachine vm = VirtualMachine.attach("85241");//加載agent并指明需要采集信息的類(lèi)和方法vm.loadAgent("trace-agent.jar", "org.xunche.app.HelloTraceAgent.sayHi");vm.detach();}private static class Server implements Runnable {@Overridepublic void run() {try {ServerSocket serverSocket = new ServerSocket(SERVER_PORT);while (true) {Socket socket = serverSocket.accept();InputStream input = socket.getInputStream();BufferedReader reader = new BufferedReader(new InputStreamReader(input));System.out.println("receive message:" + reader.readLine());}} catch (IOException e) {e.printStackTrace();}}public void start() {Thread thread = new Thread(this);thread.start();}} }運(yùn)行上面的程序,可以看到服務(wù)端收到了org.xunche.app.HelloTraceAgent.sayHi的請(qǐng)求和返回信息。
receive message:Message{response=hi, xunche, 1581599464436, request=[xunche], className='org.xunche.app.HelloTraceAgent', methodName='sayHi'}小結(jié)
和通過(guò)agentmain實(shí)現(xiàn)了一個(gè)收集運(yùn)行時(shí)方法調(diào)用信息的小工具,當(dāng)然根據(jù)篇幅和時(shí)間問(wèn)題,代碼寫(xiě)的比較隨意,大家多體會(huì)實(shí)際上,agent的作用遠(yuǎn)不止文章中介紹的這些,像 BTrace,arm,springloaded等中也都有用到agent。
轉(zhuǎn)載自Hollis
與50位技術(shù)專(zhuān)家面對(duì)面20年技術(shù)見(jiàn)證,附贈(zèng)技術(shù)全景圖總結(jié)
以上是生活随笔為你收集整理的写那么多年Java,还不知道啥是Java agent的必须看一下!的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 物联网的未来:关于物联网的10个预测
- 下一篇: 复杂网络表示的原理,算法和应用