ASM指南翻译-12 无状态转换_JAVA_编程开发_程序员俱乐部

中国优秀的程序员网站程序员频道CXYCLUB技术地图
热搜:
更多>>
 
您所在的位置: 程序员俱乐部 > 编程开发 > JAVA > ASM指南翻译-12 无状态转换

ASM指南翻译-12 无状态转换

 2011/10/25 8:12:19  aswang  http://aswang.iteye.com  我要评论(0)
  • 摘要:3.2.4无状态转换假定,我们想要测量一个程序中花费在每个类上的时间,那么我们需要在每个类上添加一个静态的计时字段,并且我们需要将每个方法的执行之间加到这个字段上。也就是说我们想转换类似于C这样的一个类:publicclassC{publicvoidm()throwsException{Thread.sleep(100);}}转换之后:publicclassC{publicstaticlongtimer;publicvoidm()throwsException{timer-=System
  • 标签:翻译

?

3.2.4无状态转换

假定,我们想要测量一个程序中花费在每个类上的时间,那么我们需要在每个类上添加一个静态的计时字段,并且我们需要将每个方法的执行之间加到这个字段上。也就是说我们想转换类似于C这样的一个类:

public class C {

???????? public void m() throws Exception {

???????? ???????? Thread.sleep(100);

???????? }

}

转换之后:

public class C {

???????? public static long timer;

???????? public void m() throws Exception {

?????????????????? timer -= System.currentTimeMillis();

?????????????????? Thread.sleep(100);

?????????????????? timer += System.currentTimeMillis();

???????? }

}

?

为了弄清楚ASM是如何实现的,我们将编译这两个类,然后对比它们的TraceClassVisitor或者ASMifierClassVisitor的输出。通过TraceClassVisitor我们可以发现以下不同(粗体表示):

GETSTATIC C.timer : J

INVOKESTATIC java/lang/System.currentTimeMillis()J

LSUB

PUTSTATIC C.timer : J

LDC 100

INVOKESTATIC java/lang/Thread.sleep(J)V

GETSTATIC C.timer : J

INVOKESTATIC java/lang/System.currentTimeMillis()J

LADD

PUTSTATIC C.timer : J

RETURN

MAXSTACK = 4

MAXLOCALS = 1

?

通过上面我们看到了,我们必须在这个方法的最开始添加四条指令,以及在这个方法返回之前添加另外四条指令。最后 我们需要更新操作数栈的大小。因此,我们可以在方法适配器中重写这个方法以增加最开始的四条指令:

public void visitCode() {

???????? mv.visitCode();

???????? mv.visitFieldInsn(GETSTATIC, owner, "timer", "J");

???????? mv.visitMethodInsn(INVOKESTATIC, "java/lang/System",

?????????????????? "currentTimeMillis", "()J");

???????? mv.visitInsn(LSUB);

???????? mv.visitFieldInsn(PUTSTATIC, owner, "timer", "J");

}

?

在这里owner必须设置为即将转换的类的名字。现在我们需要添加另外四条位于RETURN之前的指令,同时这四条指令也必须位于xRETURN或者ATHROW之前,因为这些指令都会结束方法的执行。这些指令没有任何参数,并且都是通过visitInsn方法来访问。因此,我们可以重写这个方法来添加这四条指令:

public void visitInsn(int opcode) {

???????? if ((opcode >= IRETURN && opcode <= RETURN) || opcode == ATHROW) {

?????????????????? mv.visitFieldInsn(GETSTATIC, owner, "timer", "J");

?????????????????? mv.visitMethodInsn(INVOKESTATIC, "java/lang/System",

?????????????????? "currentTimeMillis", "()J");

?????????????????? mv.visitInsn(LADD);

?????????????????? mv.visitFieldInsn(PUTSTATIC, owner, "timer", "J");

???????? }

???????? mv.visitInsn(opcode);

}

?

最后,我们仍然需要更新操作数栈的大小。这些指令中,我们往操作数栈中放置了两个long行数据,因此需要四个方框。在这个方法开始的时候,这个操作数栈是被初始化为空的,因此我们知道最开始的四条指令需要的栈大小为4.我们也知道,我们插入的代码对这个栈大小没有改变(因为从栈中弹出的数据与入栈的数据一样多)。结果是,如果最初的代码需要的栈大小为s,那么转换方法之后栈的大小的最大值为max(4,s)。不幸地是,我们还需要在RETURN指令之前添加四条指令,在这里我们不知道这个操作数栈的大小。我们只知道它的大小应该小于等于s。最后,我们可以认为在RETURN指令之前添加的代码需要的操作数栈的大小最大为s+4。这种最坏的情形在实际中很少发生:就一般的编译器而言,RETURN指令之前,操作数栈中一般只包含方法的返回值,那么它的大小就可能是0,1或者最大为2.但是,如果我们希望处理所有可能的情况,那么我们应该使用最坏的这种情况。所以,我们需要重写visitMaxs方法:

public void visitMaxs(int maxStack, int maxLocals) {

???????? mv.visitMaxs(maxStack + 4, maxLocals);

}

?

当然了,我们也不必为操作数栈的大小而烦恼,我们可以依赖于COMPUTE_MAXS选项,它会为我们计算最优的值而不是最坏的值。同时,就这么一个简单的转换,也没必须花费这么经理来手动更新maxStack

?

现在,一个有趣的问题是:如何处理栈映射帧?原始的代码不包含任何帧,转换后的代码页不包含,难道这是因为我们使用的例子很特殊吗?这里是否有一些情形帧必须被更新吗?答案是否定的,因为,第一,插入的代码没有改变操作数栈,第二,插入的代码没有包含跳转指令,第三,这些跳转指令,或者更正式的,程序的控制流没有修改。这意味着原始的帧没有改变,既然新插入的代码没有增加帧,那么原始帧压缩后也没有改变。

?

现在,我们可以将这些元素和ClassAdapter以及MethodAdapter结合起来:

public class AddTimerAdapter extends ClassAdapter {

???????? private String owner;

???????? private boolean isInterface;

???????? public AddTimerAdapter(ClassVisitor cv) {

?????????????????? super(cv);

???????? }

???????? @Override public void visit(int version, int access, String name,

?????????????????? String signature, String superName, String[] interfaces) {

?????????????????? cv.visit(version, access, name, signature, superName, interfaces);

?????????????????? owner = name;

?????????????????? isInterface = (access & ACC_INTERFACE) != 0;

???????? }

???????? @Override public MethodVisitor visitMethod(int access, String name,

?????????????????? String desc, String signature, String[] exceptions) {

?????????????????? MethodVisitor mv = cv.visitMethod(access, name, desc, signature,

?????????????????? exceptions);

?????????????????? if (!isInterface && mv != null && !name.equals("<init>")) {

??????????????????????????? mv = new AddTimerMethodAdapter(mv);

?????????????????? }

???????? return mv;

???????? }

???????? @Override public void visitEnd() {

?????????????????? if (!isInterface) {

??????????????????????????? FieldVisitor fv = cv.visitField(ACC_PUBLIC + ACC_STATIC, "timer",

??????????????????????????? "J", null, null);

??????????????????????????? if (fv != null) {

???????????????????????????????????? fv.visitEnd();

??????????????????????????? }

?????????????????? }

???????? cv.visitEnd();

}

?

class AddTimerMethodAdapter extends MethodAdapter {

???????? public AddTimerMethodAdapter(MethodVisitor mv) {

?????????????????? super(mv);

???????? }

???????? @Override public void visitCode() {

?????????????????? mv.visitCode();

?????????????????? mv.visitFieldInsn(GETSTATIC, owner, "timer", "J");

?????????????????? mv.visitMethodInsn(INVOKESTATIC, "java/lang/System",

?????????????????? "currentTimeMillis", "()J");

?????????????????? mv.visitInsn(LSUB);

?????????????????? mv.visitFieldInsn(PUTSTATIC, owner, "timer", "J");

???????? }

???????? @Override public void visitInsn(int opcode) {

?????????????????? if ((opcode >= IRETURN && opcode <= RETURN) || opcode == ATHROW) {

??????????????????????????? mv.visitFieldInsn(GETSTATIC, owner, "timer", "J");

??????????????????????????? mv.visitMethodInsn(INVOKESTATIC, "java/lang/System",

??????????????????????????? "currentTimeMillis", "()J");

??????????????????????????? mv.visitInsn(LADD);

??????????????????????????? mv.visitFieldInsn(PUTSTATIC, owner, "timer", "J");

?????????????????? }

?????????????????? mv.visitInsn(opcode);

???????? }

???????? @Override public void visitMaxs(int maxStack, int maxLocals) {

?????????????????? mv.visitMaxs(maxStack + 4, maxLocals);

???????? }

???????? }

}

?

这里的类适配器是用来实例化方法适配器(除了构造方法),同时也用来添加计时字段和保存将被转换的类名到一个字段中,这样在方法适配器中可以访问到这个类名。

发表评论
用户名: 匿名