Asm Transformations

Download as pdf or txt
Download as pdf or txt
You are on page 1of 7

Using the ASM framework to implement common Java

bytecode transformation patterns


Eugene Kuleshov, eu@javatx.org

ABSTRACT the visited tree with objects. Visitors can change call chains and
Most AOP frameworks targeting the Java platform use a bytecode therefore transform the visited code. Using the Adapter design
weaving approach as it is currently considered the most practical pattern [7] visitors can be chained in order to implement complex
solution. It allows applying cross-cutting concerns to Java transformation from smaller building blocks. A similar approach
applications when source code is not available, is portable and is also used in the SAX API for XML processing [8].
works on existing JVMs, in comparison to VM-level AOP ASM hides all the complexity of the serialization and
implementations. deserialization of the class bytecode, using the following
techniques:
Load-time bytecode weaving (LTW), which happens right before
the application code is loaded into the Java VM, significantly • Automatic management of the class constant pool,
simplifies development environment, but also raises the bar for therefore the user does not have to manipulate indexes
the performance and memory requirements for these of these constants.
transformations. Such requirements directly apply to the toolkit • Automatic management of the class structure, including
that will be used to perform these transformations. In this paper, annotations, fields, methods, method code and other
we examine how Java bytecode transformations, typical for AOP standard bytecode attributes.
implementations can be done efficiently, using the ASM1
bytecode manipulation framework [1]. • Labels are used to manage instruction addresses, so it is
easy to insert new code in between existing instructions
Transformations used by general-use AOP frameworks and
similar applications can be categorized, and the common patterns • Computation of maximum stack and local variables, as
can be reused to implement specific transformations. These well as StackMapFrames
patterns can also be combined to implement more complex The event-based interaction between event producers and event
transformations. consumers is defined by several interfaces: ClassVisitor,
FieldVisitor, MethodVisitor, and AnnotationVisitor. Event
General Terms producers, like ClassReader fire visit*() calls to those interfaces.
Languages, Design, Performance, Experimentation, On the other hand, event receivers like writers (ClassWriter,
Standardization FieldWriter, MethodWriter, and AnnotationWriter), adapters
(ClassAdapter and MethodAdapter) or classes from the tree
package (ClassNode, MethodNode, etc) implementing those
Keywords interfaces.
Aspect-Oriented Programming, Java, bytecode, weaving, ASM
The following code demonstrates how this looks from the
developer’s point of view:
1. INTRODUCTION
The ASM bytecode framework was designed at France Telecom ClassReader cr = new ClasReader(bytecode);
ClassWriter cw = new ClassWiter(cr,
R&D by Eric Bruneton, Romain Lenglet and Thierry Coupaye ClassWriter.COMPUTE_MAXS |
[2]. After evaluating several existing frameworks, including ClassWriter.COMPUTE_FRAMES);
BCEL [3], Serp [4] and JOIE [5], they designed a more efficient FooClassAdapter cv = new FooClassAdapter(cw);
approach, providing better performance and memory foot print. cr.accept(cv, 0);
Today ASM is used in many applications and has become the de- Here ClassReader reads the bytecode. On accept() method call
facto standard for bytecode processing. ClassReader fires all visiting events corresponding to the
The main idea of the ASM API [6] is not to use an object bytecode structure. FooClassAdapter will receive those events
representation of the bytecode. This made it possible expressing and can change the event flow before passing them to the
the same transformations using only a few classes comparing to ClassWriter. Once ClassWriter receive all the events it will have
approximately 80 classes in Serp and 270 in BCEL API. Those transformed bytecode. You may notice that the ClassReader
frameworks create lots of objects during class deserialization, instance is passed to the ClassWriter that allows performance
which takes a lot of time and memory. ASM avoids this overhead optimizations based on the assumption that the transformations
to keep transformation fast and to use very little memory. This is mostly add new code.
done by using the Visitor design pattern [7], without representing The following sections will show a number of practical examples
that should help you to better understanding of the ASM
1
The ASM name does not mean anything: it is just a reference to framework.
the keyword in C which allows some functions to be
implemented in assembly language.
2. Accessing class data used to analyze selected methods in isolation. The result of such
The visitor-based approach allows capturing class data analysis can be also used for inter-method analysis.
incrementally, collecting only the information required for Combining those features it is possible to retrieve required class
specific use case without creating and destroying lots of short- information with very controlled memory overhead by making
lived objects. Incremental processing allows decisions such as decisions on the required transformations at load time.
whether or not part of the class needs to be transformed, skipping
In the next sections we will see concrete examples of the bytecode
parsing of class parts that don’t need to be transformed.
transformations typical for AOP.
ASM provide very simple API to support this. First of all there
are several bit-mask flags in the ClassReader.accept() method: 3. COMMON TRANSFORMATIONS
AOP frameworks that are using load time transformations usually
• SKIP_DEBUG – Used to ignore debug info, such as
build their own high-level abstractions about how application
source file, line number and variable info.
code needs to be transformed. Those abstractions can be
• SKIP_FRAMES – Used to ignore StackMapTable decomposed into smaller building blocks that can be chained
information used for Java 6 split bytecode verifier. together to achieve the required transformation. Here are most
• EXPAND_FRAMES – Expand the StackMapTable common use cases:
data, allowing visitor to have information on types of all • Class Transformations
local variables and current stack slots.
• Introduce Interface
• SKIP_CODE – Exclude code of all methods from
• Add a New Field
visiting, while still passing trough method’s and
parameter’s attributes and annotations. • Add a New Method
Additionally, the visitor can decide to skip the corresponding • Replace Method Body
bytecode section it is not interested in. In order to do that,
• Merge Two Classes into One
visitField(), visitMethod() and visitAnnotation() methods that
returns nested visitor can return null. That will indicate to the • Method Transformations
bytecode producer to skip the corresponding class element. • Insert Code before Method, Constructor or Static
When there is no need to read class data, but just the class Initializer Execution
hierarchy, ClassReader provides shortcut methods • Insert Code before Method Exit
getSuperName() and getInterfaces() to read super class and
implemented interfaces, respectively. • Replace Field Access
It is also possible use the tree package of ASM framework to read • Replace Method Call
the entire class or selected method in memory and change its • Inline Method
structures using DOM-like API. For example:
ClassReader cr = new ClassReader(source); 3.1 Class Transformations
ClassWriter cw = new ClassWriter();
ClassAdapter ca = new ClassAdapter(cw) { 3.1.1 Introducing Interface
public MethodVisitor visitMethod(int access, This transformation only changes the class information about the
String name, String desc, implemented interfaces. We can use simple ClassAdapter for this:
String signature, String[] exceptions) {
final MethodVisitor mv = public class InterfaceAdder extends ClassAdapter {
super.visitMethod(access, private Set newInterfaces;
name, desc, signature, exceptions);
MethodNode mn = new MethodNode(access, public InterfacesAdder(ClassVisitor cv,
name, desc, signature, exceptions) { Set newInterfaces) {
public void visitEnd() { super(cv);
// transform/analyze this method DOM this.newInterfaces = newInterfaces;
accept(mv); }
}
public void visit(int version, int access,
}; String name, String signature,
return mn; String superName, String[] interfaces) {
} Set ints = new HashSet(newInterfaces);
}; ints.addAll(Arrays.asList(interfaces));
cr.accept(ca, 0);
cv.visit(version, access, name, signature,
The above code will basically suspend passing method events to superName, (String[]) ints.toArray());
the next visitor in the chain (i.e. ClassWriter in this case) until the }
}
visitEnd() event. When visitEnd() is passed, the MethodNode
instance will contain the entire method data. At this point the Note, the actual methods required to implement the introduced
method data can be transformed and only after that, finally passed interfaces must be added with a separate transformation, which
to the next visitor using the MethodNode.accept() method. will be discussed shortly.
It is worth mentioning that ASM provides several basic Data Flow
Analysis algorithms that work on the tree package and can be
3.1.2 Adding a New Field 3.1.4 Replace Method Body
Adding new fields to an existing class is a common This transformation is a variation of the transformation for adding
transformation. Usually it is used to enrich class state with a new method. In this case, the difference is that new method
additional data. Here is a ClassAdapter that adds new field: generation is triggered by the visitMethod() call for a method that
public class FieldAdder extends ClassAdapter { needs to be renamed, as opposed to visitEnd() event:
private final FieldNode fn; public class MethodReplacer extends ClassAdapter {
private String mname;
public FieldAdder(ClassVisitor cv, private String mdesc;
FieldNode fn) { private String cname;
super(cv);
this.fn = fn; public MethodReplacer(ClassVisitor cv,
} String mname, String mdesc) {
super(cv);
public void visitEnd() { this.mname = mname;
fn.accept(cv); this.mdesc = mdesc;
super.visitEnd(); }
}
} public void visit(int version, int access,
String name, String signature,
The above adapter introduces new events in the visitEnd() String superName, String[] interfaces) {
method, which is called at the end of visiting class data. So, the this.cname = name;
field will be added after existing fields, which is the safest way, cv.visit(version, access, name,
just in case some other code relies on the field order. In the above signature, superName, interfaces);
code new events are fired by FieldNode instance on accept() call. }
This allows reusing field definition, including annotations or even public MethodVisitor visitMethod(int access,
custom attributes, in case if same field need to be added to many String name, String desc,
classes. String signature, String[] exceptions) {
String newName = name;
Note that non-static field initialization should be done separately, if(name.equals(mname) && desc.equals(mdesc)) {
for example by adding code after constructor invocation, or for newName = "orig$" + name;
static fields, at the end of the static block. Those transformations generateNewBody(access, desc, signature,
will be discussed later in the paper. exceptions, name, newName);
}
3.1.3 Adding a New Method return super.visitMethod(access, newName,
desc, signature, exceptions);
New methods can be added for implementing newly introduced }
interface or for internal needs. The class adapter for this can also
use visitEnd() method to add new method to the class. private void generateNewBody(int access,
String desc, String signature,
public class MethodAdder extends ClassAdapter {
String[] exceptions,
private int mAccess;
String name, String newName) {
private String mName;
MethodVisitor mv = cv.visitMethod(access,
private String mDesc;
private String mSignature; name, desc, signature, exceptions);
private String[] mExceptions; // ...
mv.visitCode();
public MethodAdder(ClassVisitor cv, // call original metod
int mthAccess, String mthName, mv.visitVarInsn(Opcodes.ALOAD, 0); // this
String mthDesc, String mthSignature, mv.visitMethodInsn(access, cname, newName,
String[] mthExceptions) { desc);
super(cv); // ...
this.mAccess = mthAccess; mv.visitEnd();
this.mName = mthName; }
this.mDesc = mthDesc; }
this.mSignature = mthSignature;
this.mExceptions = mthExceptions; In the above visitMethod() checks method body should be
} replaced. If it should, it uses generates new method body and then
replaces the method name and delegates to the next visitor in the
public void visitEnd() { chain. This way body of the original method will be saved in the
MethodVisitor mv = cv.visitMethod(mAccess,
mName, mDesc, mSignature, mExceptions); newly created method.
// create method body
mv.visitMaxs(0, 0); 3.1.5 Merging Two Classes into One
mv.visitEnd(); When adding the implementation of an introduced interface to
super.visitEnd(); transformed class, it is convenient to use an existing
} implementation of these methods from a separately compiled
} class. This second can be loaded into memory and used multiple
times to copy class methods and fields.
ClassReader cr = new ClasReader(bytecode);
ClassNode cn = new ClassNode();
cr.accept(cn, 0);
Then we can pass loaded ClassNode instance to the merging ...
adapter: }

public class MergeAdapter extends ClassAdapter { 3.2.1 Common Issues


private ClassNode cn;
There are several common issues method transformers have to
private String cname;
deal with. ASM provides the commons package that can make
public MergeAdapter(ClassVisitor cv, these issues easier to handle for the developer.
ClassNode cn) {
super(cv); • Complexity of generating new code. For this issue
this.cn = cn; GeneratorAdapter provides number of more high level
} building blocks that assist in the creation of common
code idioms like boxing and unboxing from primitive
public void visit(int version, int access,
String name, String signature, types into object wrappers, loading method parameters,
String superName, String[] interfaces) { and a few others.
super.visit(version, access, name,
signature, superName, interfaces); • Inserting new local variables in the middle of a method
this.cname = name; being visited is handled by LocalVariablesSorter, which
} can transparently rename method variables after new
variables are inserted in the middle of the visited
public void visitEnd() {
for(Iterator it = cn.fields.iterator(); method.
it.hasNext();) { • Dealing with the data flow and type system. There is, of
((FieldNode) it.next()).accept(this);
}
course, no single solution for all the use cases, but ASM
for(Iterator it = cn.methods.iterator(); provides number of primitives that hide this complexity
it.hasNext();) { from the developer. One such primitive is
MethodNode mn = (MethodNode) it.next(); AdviceAdapter that provides a convenient way to detect
String[] exceptions = the right place in the bytecode to insert new code at the
new String[mn.exceptions.size()];
mn.exceptions.toArray(exceptions); beginning of method execution and before exiting of the
MethodVisitor mv = method.
cv.visitMethod(
mn.access, mn.name, mn.desc,
Let’s look at more concrete use cases of the method
mn.signature, exceptions); transformations.
mn.instructions.resetLabels();
mn.accept(new RemappingMethodAdapter( 3.2.2 Insert Code before Method, Constructor or
mn.access, mn.desc, mv, Static Initializer Execution
new Remapper(cname, cn.name))); As already mentioned above, this transformation is greatly
}
super.visitEnd(); simplified by subclassing AdviceAdapter. The developer
} implements onMethodEnter() method called by the
} AdviceAdapter at the appropriate time. This implementation
As you can see, fields and methods are copied from ClassNode should not change stack and it could use methods inherited from
into the visited class. Moreover, types referenced from the copied LocalVariablesSorter for adding new local variables.
methods are remapped with the RemappingMethodAdapter to class EnteringAdapter extends AdviceAdapter {
appropriately fit into the new target class. private String name;
private int timeVar;
private Label timeVarStart = new Label();
3.2 Method Transformations private Label timeVarEnd = new Label();
The major difference of the method transformations from the
class-level transformations is that they require additional filtering public PrintEnteringAdapter(MethodVisitor mv,
at the class level. ClassAdapter can be used to make this decision int acc, String name, String desc) {
super(mv, acc, name, desc);
and if the method happens to be interested, additional this.name = name;
MethodAdapter can be inserted at the execution chain. For }
example:
public class FilterAdapter extends ClassAdapter { protected void onMethodEnter() {
...
visitLabel(timeVarStart);
public MethodVisitor visitMethod(
int acc, String name, String desc, int timeVar = newLocal(Type.getType("J"));
String signature, String[] exceptions) { visitLocalVariable("timeVar", "J", null,
MethodVisitor mv = cv.visitMethod(acc, name, timeVarStart, timeVarEnd, timeVar);
desc, signature, exceptions); super.visitFieldInsn(GETSTATIC,
if(isFooMethod(name, desc)) { "java/lang/System", "err",
mv = new FooMethodAdapter(mv, acc, “Ljava/io/PrintStream;”);
name, desc); super.visitLdcInsn("Entering " + name);
super.visitMethodInsn(INVOKEVIRTUAL,
}
"java/io/PrintStream", "println",
return mv;
"(Ljava/lang/String;)V");
}
}
public void visitMaxs(int stack, int locals) { int maxLocals) {
visitLabel(timeVarEnd); Label endFinally = new Label();
super.visitMaxs(stack, locals); mv.visitTryCatchBlock(startFinally,
} endFinally, endFinally, null);
} mv.visitLabel(endFinally);
onFinally(ATHROW);
Note how debug information is added for the timeVar variable via mv.visitInsn(ATHROW);
the call to visitLocalVariable(), which allows us to see the value
of this variable in the debugger when stepping trough transformed mv.visitMaxs(maxStack, maxLocals);
class in the debugger. }

3.2.3 Insert Code before Method Exit protected void onMethodExit(int opcode) {
if(opcode!=ATHROW) {
Transformation for inserting code before the method exists is very
onFinally(opcode);
similar to the transformation used to insert code before the start of }
the method. This time developer provides implementation of the }
onMethodExit() method. However, one difference is that opcode
for the instruction that caused visited method to exit is passed as a private void onFinally(int opcode) {
parameter to onMethodExit() call and could be one of RETURN, mv.visitFieldInsn(GETSTATIC,
"java/lang/System", "err",
IRETURN, FRETURN, ARETURN, LRETURN, DRETURN or
"Ljava/io/PrintStream;");
ATHROW. More over, for all opcodes except RETURN, at the mv.visitLdcInsn("Exiting " + name);
time onMethodExit() invoked, the top stack slot has the value that mv.visitMethodInsn(INVOKEVIRTUAL,
will be returned from the method, or exception that will be thrown "java/io/PrintStream", "println",
when opcode is ATHROW: "(Ljava/lang/String;)V");
}
class ExitingAdapter extends AdviceAdapter { }
private String name;
Note that try/finally boundaries are defined by visitLabel() calls
public ExitingAdapter(MethodVisitor mv, after visitCode() and before visitMaxs() calls, respective, for the
int acc, String name, String desc) {
super(mv, acc, name, desc);
start and the end of the try/finally block.
this.name = name;
} 3.2.4 Replace Field Access
Field access can be replaced with a method call in order to
public void onMethodExit(int opcode) { provide additional logic. A static delegation method can be
mv.visitFieldInsn(GETSTATIC, created to substitute static field access and delegation instance
"java/lang/System", "err",
"Ljava/io/PrintStream;"); method -- for substituting access to instance fields.
if(opcode==ATHROW) { Transformation of the method code looks like this:
mv.visitLdcInsn("Exiting on exception " + public class FieldAccessAdapter
name); extends MethodAdapter implements Opcodes {
} else { private final String cname;
mv.visitLdcInsn("Exiting " + name); private final Map adapters;
}
mv.visitMethodInsn(INVOKEVIRTUAL, public FieldAccessAdapter(MethodVisitor mv,
"java/io/PrintStream", "println", String cname, Map adapters) {
"(Ljava/lang/String;)V"); super(mv);
} this.cname = cname;
} this.adapters = adapters;
}
Obviously, this code won’t catch the exception thrown from
nested method calls and passed trough the caller method. To catch public void visitFieldInsn(int opcode,
this case, we’ll need to introduce try/finally block for the entire String owner, String name, String desc) {
method body. In order to do that, we could use variant of the Info info = matchingInfo(opcode, owner,
above adapter: name, desc);
if(info!=null) {
super.visitMethodInsn(INVOKESTATIC,
class FinallyAdapter extends AdviceAdapter { cname, info.adapterName,
private String name; info.adapterDesc);
private Label startFinally = new Label(); return;
}
public FinallyAdapter(MethodVisitor mv,
super.visitFieldInsn(opcode, owner,
int acc, String name, String desc) {
name, desc);
super(mv, acc, name, desc);
}
this.name = name;
...
}
Note that delegation methods should be generated separately
public void visitCode() { using transformation for adding new methods to class described
super.visitCode();
mv.visitLabel(startFinally); above.
}

public void visitMaxs(int maxStack,


3.2.5 Replace Method Call String desc, MethodVisitor mv, MethodNode mn,
String oldClass, String newClass) {
Method call replacement is a common use case. This simple super(access, desc, mv);
transformation can be simplified even more if the method call is this.oldClass = oldClass;
replaced with a static delegation method in the same class. In that this.newClass = newClass;
case, the method size won’t increase and the method this.mn = mn;
}
transformation can be implemented like this:
public class MethodCallAdapter public void visitMethodInsn(int opcode,
extends MethodAdapter implements Opcodes { String owner, String name, String desc) {
private final String cname; if(!canBeInlined(owner, name, desc)) {
private final Set infos; mv.visitMethodInsn(opcode,
owner, name, desc);
public MethodCallAdapter(MethodVisitor mv, return;
String cname, Set infos) { }
super(mv);
this.cname = cname; Map map = Collections.singletonMap(
this.infos = infos; oldClass, newClass);
} Remapper remapper = new Remapper(map);
Label end = new Label();
public void visitMethodInsn(int opcode, inlining = true;
String owner, String name, String desc) { mn.instructions.resetLabels();
Info info = matchingInfo(opcode, owner, mn.accept(new InliningAdapter(this,
name, desc); opcode==Opcodes.INVOKESTATIC ?
if(info!=null) { Opcodes.ACC_STATIC : 0,
super.visitMethodInsn(INVOKESTATIC, desc, remapper, end));
cname, info.adapterName, inlining = false;
info.adapterDesc); super.visitLabel(end);
return }
}
super.visitMethodInsn(opcode, owner, public void visitTryCatchBlock(Label start,
name, desc); Label end, Label handler, String type) {
} if(!inlining) {
... blocks.add(new CatchBlock(start, end,
} handler, type));
Note that this transformation can’t be used to intercept class } else {
super.visitTryCatchBlock(start, end,
construction (new in Java language). In the bytecode object handler, type);
construction represented by two separate instructions that can be }
far apart in the steam of events. First one is NEW opcode that }
creates not-initialized object instance of specified type. Before
public void visitMaxs(int stack, int locals) {
that instance could be used, <init> method of that instance has to Iterator it = blocks.iterator();
be called using INVOKESPECIAL opcode. Code generated by while(it.hasNext()) {
java compilers make this even more complicated because result of CatchBlock b = (CatchBlock) it.next();
NEW opcode is duped on the stack and duplicated reference is super.visitTryCatchBlock(b.start, b.end,
used as a result value. So, a transformation like this will obviously b.handler, b.type);
have to use some state machine or load the entire method in }
super.visitMaxs(stack, locals);
memory (i.e. using ASM tree package) and transform it directly. }
Such an example is beyond the scope of this paper. We are }
looking into the ways to generalize it and include into the ASM
The above adapter extends LocalVariablesSorter in order to
commons package.
handle local variables from the inlined code. It is also captures
3.2.6 Inline Method visitTryCatchBlock() calls the end of the method and replay them
This transformation is very similar to the one used for merging back there. Inlined method code, icluding try/catch blocks and
two classes. So, we can also use content of the MethodNode to local variables is inserted from the MethodNode, decorared by
insert inlined code into some other method. However in this case InliningAdapter. This adapter does all type renaming and
we not only need to rename types, but we also need to replace all replacing of the RETURN opcodes is done in a nested adapter:
RETURN opcodes with jumps to the end of the method and make public static class InliningAdapter
sure that try/catch blocks are in the right order. Here is an adapter extends RemappingMethodAdapter {
private final LocalVariablesSorter lvs;
that does that: private final Label end;
public class MethodCallInliner
extends LocalVariablesSorter { public InliningAdapter(LocalVariablesSorter mv,
private final String oldClass; Label end, int acc, String desc,
private final String newClass; Remapper remapper) {
private final MethodNode mn; super(acc, desc, mv, remapper);
this.lvs = mv;
private List blocks = new ArrayList();
this.end = end;
private boolean inlining;
int off = (acc & Opcodes.ACC_STATIC)!=0 ?
private MethodCallInliner(int access, 0 : 1;
Type[] args = Type.getArgumentTypes(desc); The following table present results for running several ASM
for (int i = args.length-1; i >= 0; i--) { helper classes and transformations on the same classes:
super.visitVarInsn(args[i].getOpcode(
Opcodes.ISTORE), i + offset); ASM 3.x class info 0.04 sec
}
if(offset>0) { ASM 3.x SerialVersionUIDAdder 1.05 sec
super.visitVarInsn(Opcodes.ASTORE, 0); ASM 3.x LocalVariablesSorter 1.29 sec
}
} ASM 3.x analyze with SimpleVerifier 9.05 sec
public void visitInsn(int opcode) { 5. CONCLUSIONS
if(opcode==Opcodes.RETURN) {
super.visitJumpInsn(Opcodes.GOTO, end); This paper demonstrated number of Java bytecode
} else { transformations that can be easily assembled together to
super.visitInsn(opcode); implement AOP solutions. The examples shown, implemented
} using the ASM framework, demonstrate the power and simplicity
}
of the ASM framework. Using ASM allows developers to focus
public void visitMaxs(int stack, int locals) { on code transformations instead of spending time on low level
} bytecode manipulations.
protected int newLocalMapping(Type type) { The ASM framework is the de-facto standard for high-
return lvs.newLocal(type); performance bytecode transformations, and it is used in many
} Java-based applications and frameworks including code analyzers
} (SonarJ, IBM AUS), ORM mappers (including Oracle TopLink
Note that this adapter loads method arguments from the stack into and Berkley DB, ObjectWeb EasyBeans and Speedo), and
remapped local variables and also removes the visitMaxs() event. scripting languages (BeanShell, Groovy and JRuby).

4. Performance 6. ACKNOWLEDGEMENTS
It is hard to provide direct performance comparison with other First I would like to thank ASM team and all users of the ASM
frameworks. We are trying to maintain performance suite for null framework for being such a great community. I also want to thank
transformation for ASM, BCEL, SERP and Javassist and compare Tim Eck, the lead developer of Terracotta DSO, and Jason van
results with the overhead of ASM transformations from the Zyl, from Maven project for feedback on this paper.
commons package. Here are the results of running those tests
using Java 6 on Windows PS with Intel Duo 2.33Gz processor for
reading and writing back 15840 classes from the rt.jar (not
7. REFERENCES
[1] The ASM project web site, http://asm.objectweb.org/
including file I/O):
[2] E. Bruneton, R. Lenglet, T. Coupaye. ASM: a code
ASM 3.x with copy pool 0.56 sec
manipulation tool to implement adaptable systems.
ASM 3.x compute maxs 1.43 sec [3] M. Dahm, Byte Code Engineering, Proceedings JIT’99,
ASM 3.x compute frames 2.67 sec Springer, 1999.
ASM 3.x tree package 1.77 sec [4] A. White, Serp, http://serp.sourceforge.net
BCEL 5.2 16.19 sec [5] G. A. Cohen, J. S. Chase, D. L. Kaminsky, Automatic
program transformation with JOIE, USENIX 1998 Annual
BCEL 5.2 compute maxs 18.39 sec
Technical Conference, New Orleans, Louisiana, USA, 1998.
BCEL Aspectj 1.5.3 4.77 sec [6] E. Kuleshov. Using ASM toolkit for bytecode manipulation.
BCEL Aspectj 1.5.3 compute maxs 5.93 sec http://www.onjava.com/lpt/a/5250
Javassist 3.4 2.70 sec [7] E. Gamma, R. Helm, R. Johnson, J. Vlissides. Design
Patterns: Elements of Reusable Object-Oriented Software.
Serp 1.12.1 15.90 sec
Addison-Wesley Professional Computing Series. 1995.
[8] The SAX project. http://www.saxproject.org/

You might also like