早上六点多就醒,睡不着。本人没有睡懒觉的能力,杯具....
可能大家对btrace比较熟悉,是一款就能在不改动当前程序的情况下,运行时的去监控Java程序的执行状况,例如可以做到内存状况的监控、方法调用的监控等等。实现机制是attach api + asm + instrumentation。Java Instrutment一个是允许在类加载之前,修改类字节,从JDK5中开始提供,随JVM启动的Agent,另外一个是在类加载之后,触发JVM重新进行类加载,JDK6中开始提供,用于JVM启动之后通过Attach去加载Agent。这里目前只说明第一种。
为了能够在加载前方便修改字节码,选择Asm,这需要jvm字节码结构和语法有一定了解。自己目前也不是很太熟悉,写出复杂的修改字节码的代码还是有一定难度,但幸运的是asm有一个eclipse插件,一段java代码,能够看到asm生成这段代码字节码的asm代码。在下面我要演示获取一段代码执行的时间,

long startTime = System.nanoTime();

long endTime = System.nanoTime();
System.out.println("消耗时间为(纳秒):" + (endTime - startTime));



生成的asm代码如下图,有这些信息,我们就可以方便写出ClassFileTransformer。


[img]http://dl.iteye.com/upload/attachment/275724/d563d65f-f24c-39f5-b5d8-c1bbb4f6112f.jpg[/img]



下面是实例,具体是参考ayufox的[url=http://ayufox.iteye.com/blog/655619][Java性能剖析]JPDA 4)Java Instrutment[/url],在此表示感谢,写了不少好的文章,希望以后又更多的分享。我这篇文章就是自己的一个学习笔记。:)



1)定义入口


public class StubPreMain {
	//另外一种入口格式是public static void premain(String agentArgs)
    public static void premain(String agentArgs, Instrumentation inst)
            throws ClassNotFoundException, UnmodifiableClassException  {
        inst.addTransformer(new StubTransformer());
        System.out.println("StubPreMain:Add StubTransformer");
    }
}



我们的入口类格式必须是如上的premain(对应JVM TI的Agent_OnLoad方法),与JVM TI类似,JVM启动的时候回回调这个入口函数。在premain中我们最常见就是增加Transform,Transform允许我们在类加载器修改bytecode。一般性能剖析程序都是通过修改字节码,在方法进入和退出时收集时间数据来得出剖析数据的。


2)编写Transform


public class StubTransformer implements ClassFileTransformer {
	public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
		if ("com/starit/ch02/MethodTest".equals(className)) {
			System.out.println("Load MethodTdest From Transformer");

			return readByte(className.replace("/", "."));
		}
		return null;
	}

	public byte[] readByte(String fileName) {
		System.out.println("File name:" +  fileName);
		ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
		AddTimeClassAdapter ca = new AddTimeClassAdapter(cw);
		try {
			ClassReader cr = new ClassReader(fileName);
			cr.accept(ca,  ClassReader.SKIP_DEBUG);
		} catch (IOException e) {
			e.printStackTrace();
		}

		return cw.toByteArray();
	}
}

class AddTimeClassAdapter extends ClassAdapter {
	public AddTimeClassAdapter(ClassVisitor cv) {
		super(cv);
	}

	@Override public MethodVisitor visitMethod(int access, String name,
		String desc, String signature, String[] exceptions) {
		MethodVisitor mv = cv.visitMethod(access, name, desc, signature,exceptions);
		if (name.equals("hello")) {
			mv = new AddTimerMethodAdapter(mv);
		}
		return mv;
	}
}

class AddTimerMethodAdapter extends MethodAdapter {
	public AddTimerMethodAdapter(MethodVisitor mv) {
		super(mv);
	}
	@Override public void visitCode() {
		mv.visitCode();
		Label l0 = new Label();
		mv.visitLabel(l0);
		Label l1 = new Label();
		mv.visitLabel(l1);
		Label l4 = new Label();
		mv.visitLabel(l4);
		mv.visitLocalVariable("this", "Lcom/starit/ch02/MethodTest;", null, l0, l4, 0);
		mv.visitLocalVariable("startTime", "J", null, l1, l4, 1);
		mv.visitMethodInsn(INVOKESTATIC, "java/lang/System","nanoTime", "()J");
		mv.visitVarInsn(Opcodes.LSTORE, 1);
	}
	@Override public void visitInsn(int opcode) {
		Label l2 = new Label();
		mv.visitLabel(l2);
		Label l4 = new Label();
		mv.visitLabel(l4);
		mv.visitLocalVariable("endTime", "J", null, l2, l4, 3);
		mv.visitMethodInsn(INVOKESTATIC, "java/lang/System","nanoTime", "()J");
		mv.visitVarInsn(Opcodes.LSTORE, 3);

		mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
		mv.visitTypeInsn(NEW, "java/lang/StringBuilder");
		mv.visitInsn(DUP);
		mv.visitLdcInsn("\u6d88\u8017\u65f6\u95f4\u4e3a\uff08\u7eb3\u79d2\uff09\uff1a");
		mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "(Ljava/lang/String;)V");
		mv.visitVarInsn(LLOAD, 3);
		mv.visitVarInsn(LLOAD, 1);
		mv.visitInsn(LSUB);
		mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(J)Ljava/lang/StringBuilder;");
		mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;");
		mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V");
		mv.visitInsn(opcode);
	}
}



这里简单地使用一个编译好的新的class的字节替换掉旧的class的字节,


3)修改META-INF/ MANIFEST.MF


Manifest-Version: 1.0
Premain-Class: com.starit.StubPreMain



4)打成InstrutmentTest.jar,在启动参数中增加如下启动参数,启动JVM,当加载com/starit/ch02/MethodTest这个类的时候,字节码会变成我们传进去的新的字节码


-javaagent:F:/Genuitec/workspace/ASM/InstrutmentTest.jar



5)MethodTest代码:


public class MethodTest {
	public void hello() {
		System.out.println("Hello World");
	}
}



5)运行后显示的为:


StubPreMain:Add StubTransformer
Load MethodTdest From Transformer
File name:com.starit.ch02.MethodTest
Hello World
消耗时间为(纳秒):31569




了解以后,需要我们找到他的应用点,才能发挥其作用。