Java Agent指的是一种能够以独立的模块形态运行的Java程序,它可以在应用程序运行期间在代码层面上监测应用程序的运行情况,记录应用程序运行过程中的各种参数和信息,这些信息对于分析系统性能、查找故障等都有着非常重要的意义。本文将从以下两个方面详细讲解Java Agent探针技术的应用。
Java Agent探针技术的基本原理
Java Agent探针技术可以分为两个部分,一个是Agent程序,另一个是被监测的应用程序。Agent程序与应用程序分开运行,其实现方式是通过Java Virtual Machine(JVM)的-Instrumentation参数加载Agent Jar包,然后指定Agent程序。Agent程序与应用程序共享同一个JVM,它们之间通过Java API进行通信。当在应用程序中执行到需要被监测的代码时,Agent程序会打上探针标记,然后调用相应的逻辑进行具体监测与记录操作,最终输出监测数据。
示例
示例一:Java Agent实现应用性能监测
以监测方法耗时为例,首先需要在应用程序模块的Java代码中打印出方法执行开始和结束的时间戳:
long startTime = System.currentTimeMillis();
// 方法体
long endTime = System.currentTimeMillis();
System.out.println("Method executed in " + (endTime - startTime) + " ms");
然后,编写Java Agent程序,加载到应用程序的JVM中,并在其中通过Instrumentation类提供的API监听应用程序的方法执行情况,记录下每个方法的执行时间:
public class MyAgent {
static Instrumentation is;
public static void premain(String args, Instrumentation inst) {
is = inst;
}
public static void agentmain(String args, Instrumentation inst) {
is = inst;
}
public static void main(String[] args) {
// 程序主逻辑
}
public static void methodExecuteTime(Class loadedClass, String methodName, long costTime) {
System.out.println(loadedClass.getName() + "#" + methodName + " executed in " + costTime + " ms");
}
public static void premain(String args, Instrumentation inst) {
System.out.println("MyAgent premain method invoked");
inst.addTransformer(new ClassFileTransformer() {
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer) {
className = className.replace("/", ".");
Class<?> cl = null;
try {
cl = Class.forName(className);
} catch (Exception e) {
e.printStackTrace();
}
for (Method method : cl.getDeclaredMethods()) {
is.addTransformer(new Transformer(method.getName(), cl.getName()));
}
return classfileBuffer;
}
});
try {
is.retransformClasses(getLoadedClasses());
} catch (Exception e) {
e.printStackTrace();
}
}
}
class Transformer implements ClassFileTransformer {
private String methodName;
private String className;
public Transformer(String methodName, String className) {
this.methodName = methodName;
this.className = className;
}
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer) {
ClassReader cr = new ClassReader(classfileBuffer);
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
ClassVisitor cv = new TraceClassVisitor(cw, new PrintWriter(System.out));
MethodVisitor mv = new MyMethodVisitor(cv, methodName, className);
cr.accept(mv, 0);
return cw.toByteArray();
}
}
class MyMethodVisitor extends AdviceAdapter {
private int startTimeId = -1;
private String methodName = null;
private String className = null;
public MyMethodVisitor(MethodVisitor mv, String methodName, String className) {
super(ASM5, mv, Opcodes.ACC_PUBLIC, methodName, "()V");
this.methodName = methodName;
this.className = className;
}
protected void onMethodEnter() {
mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
startTimeId = newLocal(Type.LONG_TYPE);
mv.visitVarInsn(LSTORE, startTimeId);
}
protected void onMethodExit(int opcode) {
mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
mv.visitVarInsn(LLOAD, startTimeId);
mv.visitInsn(LSUB);
mv.visitVarInsn(LSTORE, startTimeId);
mv.visitLdcInsn(Type.getType("L" + className + ";"));
mv.visitLdcInsn(methodName);
mv.visitVarInsn(LLOAD, startTimeId);
mv.visitMethodInsn(INVOKESTATIC, "com/github/hcsp/jaagent/MyAgent", "methodExecuteTime", "(Ljava/lang/Class;Ljava/lang/String;J)V", false);
}
}
接下来,运行应用程序时增加Java Agent的启动参数,例如:
java -javaagent:/path/to/myagent.jar com.example.ApplicationMain
最后,查看应用程序的日志,即可看到每个方法的执行时间。
示例二:Java Agent实现代码追踪
以追踪方法调用流程为例,首先需要在应用程序模块的Java代码中打印出方法调用层次和参数信息:
StackTraceElement[] stackTraceList = Thread.currentThread().getStackTrace();
for (int i = 0; i < stackTraceList.length; i++) {
StackTraceElement stackTrace = stackTraceList[i];
System.out.println("at " + stackTrace.getClassName() + "#" + stackTrace.getMethodName() +
"(" + stackTrace.getFileName() + ":" + stackTrace.getLineNumber() + ")");
}
然后,编写Java Agent程序,加载到应用程序的JVM中,并在其中通过Instrumentation类提供的API监听应用程序的方法执行情况,记录下每个方法的调用信息:
public class MyAgent {
static Instrumentation is;
public static void premain(String args, Instrumentation inst) {
is = inst;
}
public static void agentmain(String args, Instrumentation inst) {
is = inst;
}
public static void main(String[] args) {
// 程序主逻辑
}
public static void methodInvoke(String className, String methodName, Object... args) {
System.out.println("invoke " + className + "#" + methodName + " method" +
(args != null && args.length > 0 ? ", params: " + Arrays.toString(args) : ""));
}
public static void premain(String args, Instrumentation inst) {
System.out.println("MyAgent premain method invoked");
inst.addTransformer(new ClassFileTransformer() {
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer) {
className = className.replace("/", ".");
Class<?> cl = null;
try {
cl = Class.forName(className);
} catch (Exception e) {
e.printStackTrace();
}
for (Method method : cl.getDeclaredMethods()) {
is.addTransformer(new Transformer(method.getName(), cl.getName()));
}
return classfileBuffer;
}
});
try {
is.retransformClasses(getLoadedClasses());
} catch (Exception e) {
e.printStackTrace();
}
}
}
class Transformer implements ClassFileTransformer {
private String methodName;
private String className;
public Transformer(String methodName, String className) {
this.methodName = methodName;
this.className = className;
}
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer) {
ClassReader cr = new ClassReader(classfileBuffer);
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
ClassVisitor cv = new TraceClassVisitor(cw, new PrintWriter(System.out));
MethodVisitor mv = new MyMethodVisitor(cv, methodName, className);
cr.accept(mv, 0);
return cw.toByteArray();
}
}
class MyMethodVisitor extends AdviceAdapter {
private String methodName = null;
private String className = null;
public MyMethodVisitor(MethodVisitor mv, String methodName, String className) {
super(ASM5, mv, Opcodes.ACC_PUBLIC, methodName, "()V");
this.methodName = methodName;
this.className = className;
}
protected void onMethodEnter() {
mv.visitLdcInsn(className);
mv.visitLdcInsn(methodName);
int argsCount = Type.getArgumentTypes(methodDesc).length;
mv.visitIntInsn(BIPUSH, argsCount);
mv.visitTypeInsn(ANEWARRAY, "java/lang/Object");
for (int i = 0; i < argsCount; i++) {
mv.visitInsn(DUP);
mv.visitIntInsn(BIPUSH, i);
mv.visitVarInsn(ALOAD, i + 1);
mv.visitInsn(AASTORE);
}
mv.visitMethodInsn(INVOKESTATIC, "com/github/hcsp/jaagent/MyAgent", "methodInvoke",
"(Ljava/lang/String;Ljava/lang/String;[Ljava/lang/Object;)V", false);
}
}
运行应用程序时增加Java Agent的启动参数,例如:
java -javaagent:/path/to/myagent.jar com.example.ApplicationMain
最后,查看应用程序的日志,即可看到方法调用流程和参数信息。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Java Agent探针技术详解示例 - Python技术站