JVM插码之五:Java agent+ASM实战--监控所有方法执行时间
本文建立在对instrumentation和agent有初步的了解的前提下阅读,关于这2个类的讲解在其它文章中。
这是一个maven项目,pom中需要的配置,lib中有asm的jar包
pom.xml文件:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.dxz</groupId> <artifactId>chama</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>chama</name> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>org.ow2.asm</groupId> <artifactId>asm-test</artifactId> <version>6.2</version> <scope>test</scope> </dependency> <dependency> <groupId>org.ow2.asm</groupId> <artifactId>asm-analysis</artifactId> <version>6.2</version> </dependency> <dependency> <groupId>org.ow2.asm</groupId> <artifactId>asm-commons</artifactId> <version>6.2</version> </dependency> <dependency> <groupId>org.ow2.asm</groupId> <artifactId>asm-tree</artifactId> <version>6.2</version> </dependency> <dependency> <groupId>org.ow2.asm</groupId> <artifactId>asm-util</artifactId> <version>6.2</version> </dependency> <dependency> <groupId>javassist</groupId> <artifactId>javassist</artifactId> <version>3.12.1.GA</version> </dependency> <!-- https://mvnrepository.com/artifact/cglib/cglib --> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.2.5</version> </dependency> <!-- https://mvnrepository.com/artifact/oro/oro --> <dependency> <groupId>oro</groupId> <artifactId>oro</artifactId> <version>2.0.8</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.8</source> <target>1.8</target> <encoding>utf-8</encoding> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>3.0.0</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <manifestEntries> <!-- <Premain-Class>com.dxz.chama.javaagent.patter.TimeMonitorPatterAgent</Premain-Class> --> <!-- <Premain-Class>com.dxz.chama.javaagent.StatAgent</Premain-Class> --> <Premain-Class>com.dxz.chama.javaagent.asm.UdAgent</Premain-Class> </manifestEntries> </transformer> </transformers> </configuration> </execution> </executions> </plugin> </plugins> </build> </project>
打包后的agent的jar包中manifest属性如下:
agent类,只有一个方法,就是把自定义的类修改器添加到instrumentation中。
package com.dxz.chama.javaagent.asm; import java.lang.instrument.Instrumentation; public class UdAgent { public static void premain(String agentArgs, Instrumentation instrumentation){ instrumentation.addTransformer(new LogTransformer()); } }
类转换器实现:
package com.dxz.chama.javaagent.asm; import java.io.IOException; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.IllegalClassFormatException; import java.security.ProtectionDomain; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassWriter; public class LogTransformer implements ClassFileTransformer { @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { try { ClassReader cr = new ClassReader(className); ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); TimeCountAdpter timeCountAdpter = new TimeCountAdpter(cw); cr.accept(timeCountAdpter, ClassReader.EXPAND_FRAMES); return cw.toByteArray(); } catch (IOException e) { e.printStackTrace(); } return null; } }
实际修改字节码的方法,这里给每个类添加了一个字段UDASMCN,用于记录当前类的名字(方便打印信息)。同时记录每个方法的名字,以及执行时间。
package com.dxz.chama.javaagent.asm; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.commons.AnalyzerAdapter; import org.objectweb.asm.commons.LocalVariablesSorter; public class TimeCountAdpter extends ClassVisitor implements Opcodes { private String owner; private boolean isInterface; private String filedName = "UDASMCN"; private int acc = Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC + Opcodes.ACC_FINAL; private boolean isPresent = false; private String methodName; public TimeCountAdpter(ClassVisitor classVisitor) { super(ASM6, classVisitor); } @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { super.visit(version, access, name, signature, superName, interfaces); owner = name; isInterface = (access & ACC_INTERFACE) != 0; } @Override public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) { if (name.equals(filedName)) { isPresent = true; } return super.visitField(access, name, descriptor, signature, value); } @Override public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { MethodVisitor mv = cv.visitMethod(access, name, descriptor, signature, exceptions); if (!isInterface && mv != null && !name.equals("<init>") && !name.equals("<clinit>")) { methodName = name; AddTimerMethodAdapter at = new AddTimerMethodAdapter(mv); at.aa = new AnalyzerAdapter(owner, access, name, descriptor, at); at.lvs = new LocalVariablesSorter(access, descriptor, at.aa); return at.lvs; } return mv; } public void visitEnd() { if (!isInterface) { FieldVisitor fv = cv.visitField(acc, filedName, "Ljava/lang/String;", null, owner); if (fv != null) { fv.visitEnd(); } } cv.visitEnd(); } class AddTimerMethodAdapter extends MethodVisitor { private int time; private int maxStack; public LocalVariablesSorter lvs; public AnalyzerAdapter aa; public AddTimerMethodAdapter(MethodVisitor methodVisitor) { super(ASM6, methodVisitor); } @Override public void visitCode() { mv.visitCode(); mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "nanoTime", "()J", false); time = lvs.newLocal(Type.LONG_TYPE); mv.visitVarInsn(LSTORE, time); maxStack = 4; } @Override public void visitInsn(int opcode) { if (((opcode >= IRETURN && opcode <= RETURN) || opcode == ATHROW) && !isPresent) { mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "nanoTime", "()J", false); mv.visitVarInsn(LLOAD, time); mv.visitInsn(LSUB); mv.visitVarInsn(LSTORE, time); 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.visitFieldInsn(GETSTATIC, owner, filedName, "Ljava/lang/String;"); 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.visitVarInsn(LLOAD, time); 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); maxStack = Math.max(aa.stack.size() + 4, maxStack); } mv.visitInsn(opcode); } @Override public void visitMaxs(int maxStack, int maxLocals) { super.visitMaxs(Math.max(maxStack, this.maxStack), maxLocals); } } }
打包成jar包后,在另一个程序启动时调用,启动参数如下:
-javaagent:D:\study\chama\target\chama-0.0.1-SNAPSHOT.jar
执行效果:
时间单位是纳秒,可以看到每个方法执行完时,都会打印这个方法 的执行时间,以com/Main main:11457636为例,说明类com/Main的main方法执行力11毫秒。