Now that you've seen how to use the Javassist and BCEL frameworks for classworking (see the listing of previous articles in this series), I'm going to show you a practical classworking application. This application is replacing the use of reflection with classes generated at runtime and immediately loaded into the JVM. In the process of putting it together, I'm going to refer back to the first two articles of the series as well as the Javassist and BCEL coverage, so it makes a nice wrap-up for what's turned out to be a long series of articles.
Back in Part 2, I showed how reflection is many times slower than direct code for both field access and method calls. This sluggishness is not a problem for many applications, but there are always going to be cases where performance is critical. In these cases, reflection can represent a real bottleneck. Replacing reflection with statically compiled code can be very messy, though, and in some cases (as in frameworks where the classes or items accessed by reflection are supplied at runtime, rather than as part of the same build process) may even be impossible without restructuring the whole application.
Classworking gives you an alternative that combines the performance of statically compiled code with the flexibility of reflection. The basic approach here is to construct a custom class at runtime that will wrap access to the target classes (previously reached by reflection) in a way that can be used by your general-purpose code. After loading the custom class into the JVM, you're then set to run at full speed.
Listing 1 gives a starting point for the application. Here I've defined a simple
bean class, HolderBean, and an access class, ReflectAccess. The access class takes a single command line
argument that must be the name of one of the int-valued
bean class properties (value1 or value2). It increments the value of the named property, then
prints out both property values before exiting.
Listing 1. Reflecting a bean
public class HolderBean
{
private int m_value1;
private int m_value2;
public int getValue1() {
return m_value1;
}
public void setValue1(int value) {
m_value1 = value;
}
public int getValue2() {
return m_value2;
}
public void setValue2(int value) {
m_value2 = value;
}
}
public class ReflectAccess
{
public void run(String[] args) throws Exception {
if (args.length == 1 && args[0].length() > 0) {
// create property name
char lead = args[0].charAt(0);
String pname = Character.toUpperCase(lead) +
args[0].substring(1);
// look up the get and set methods
Method gmeth = HolderBean.class.getDeclaredMethod
("get" + pname, new Class[0]);
Method smeth = HolderBean.class.getDeclaredMethod
("set" + pname, new Class[] { int.class });
// increment value using reflection
HolderBean bean = new HolderBean();
Object start = gmeth.invoke(bean, null);
int incr = ((Integer)start).intValue() + 1;
smeth.invoke(bean, new Object[] {new Integer(incr)});
// print the ending values
System.out.println("Result values " +
bean.getValue1() + ", " + bean.getValue2());
} else {
System.out.println("Usage: ReflectAccess value1|value2");
}
}
}
|
Here's a pair of sample runs of ReflectAccess to
illustrate the results:
[dennis]$ java -cp . ReflectAccess value1 Result values 1, 0 [dennis]$ java -cp . ReflectAccess value2 Result values 0, 1 |
Now that I've demonstrated the reflection version of the code, I'll show you how to substitute a generated class for the use of reflection. There's a subtle problem involved in making this substitution work properly that goes back to the discussion of classloading in Part 1 of this series. The problem is that I'm going to generate a class at runtime that I want to access from the statically compiled code of the access class, but because the generated class doesn't exist for the compiler, there's no way to reference it directly.
So how can I link the statically compiled code to the generated class? The basic solution is to define a base class or interface that can be accessed by the statically compiled code, then extend that base class or implement that interface in the generated class. The statically compiled code can then make direct calls to methods, even though the methods won't actually be implemented until runtime.
In Listing 2, I've defined an interface, IAccess,
intended to provide this link for the generated code. The interface includes
three methods. The first method just sets a target object to be accessed. The
other two methods are proxies for the get and set methods used to access an int property value.
Listing 2. Interface to the glue class
public interface IAccess
{
public void setTarget(Object target);
public int getValue();
public void setValue(int value);
}
|
The intent here is that the generated implementation of the IAccess interface will provide the code to call the
appropriate get and set methods of a target class. Listing 3 shows a
sample of how this interface could be implemented, assuming that I want to access the value1 property of the Listing 1 HolderBean class:
Listing 3. Glue class sample implementation
public class AccessValue1 implements IAccess
{
private HolderBean m_target;
public void setTarget(Object target) {
m_target = (HolderBean)target;
}
public int getValue() {
return m_target.getValue1();
}
public void setValue(int value) {
m_target.setValue1(value);
}
}
|
The Listing 2 interface is designed to be used with a particular property of a particular type of object. This interface keeps the implementation code simple -- always an advantage when working with bytecode -- but means that the implementation class is very specific. There will need to be a separate implementation class for each type of object and property I want to access through this interface, which limits the use of this approach as a general replacement ffor reflection. This limitation isn't a problem as long as you only apply the technique selectively in cases where reflection performance is really a bottleneck.
Generating the implementation class for the Listing 2 IAccess interface with Javassist is easy -- I just need to create a
new class that implements the interface, add a member variable for the target
object reference, and finish by adding a no-argument constructor and the simple
implementation methods. Listing 4 shows the Javassist code to complete these steps, structured as a method call that takes the target class and get/set
method information and returns the binary representation of the constructed class:
Listing 4. Javassist glue class construction
/** Parameter types for call with no parameters. */
private static final CtClass[] NO_ARGS = {};
/** Parameter types for call with single int value. */
private static final CtClass[] INT_ARGS = { CtClass.intType };
protected byte[] createAccess(Class tclas, Method gmeth,
Method smeth, String cname) throws Exception {
// build generator for the new class
String tname = tclas.getName();
ClassPool pool = ClassPool.getDefault();
CtClass clas = pool.makeClass(cname);
clas.addInterface(pool.get("IAccess"));
CtClass target = pool.get(tname);
// add target object field to class
CtField field = new CtField(target, "m_target", clas);
clas.addField(field);
// add public default constructor method to class
CtConstructor cons = new CtConstructor(NO_ARGS, clas);
cons.setBody(";");
clas.addConstructor(cons);
// add public setTarget method
CtMethod meth = new CtMethod(CtClass.voidType, "setTarget",
new CtClass[] { pool.get("java.lang.Object") }, clas);
meth.setBody("m_target = (" + tclas.getName() + ")$1;");
clas.addMethod(meth);
// add public getValue method
meth = new CtMethod(CtClass.intType, "getValue", NO_ARGS, clas);
meth.setBody("return m_target." + gmeth.getName() + "();");
clas.addMethod(meth);
// add public setValue method
meth = new CtMethod(CtClass.voidType, "setValue", INT_ARGS, clas);
meth.setBody("m_target." + smeth.getName() + "($1);");
clas.addMethod(meth);
// return binary representation of completed class
return clas.toBytecode();
}
|
I'm not going to run through this code in any detail because, if you've been following this series, most of the operations will already look familiar (and if you haven't been following the series, check out Part 5 now for an overview of working with Javassist).
Generating the implementation class for the Listing 2 IAccess with BCEL is not quite as easy as with Javassist,
but it's still not terribly complicated. Listing 5 gives the code for this purpose.
This code uses the same sequence of operations as the Listing 4 Javassist code,
but runs somewhat longer because of the need to spell out each bytecode
instruction for BCEL. As with the Javassist version, I'm going to skip over the
details of the implementation (refer back to
Part 7
for an overview of BCEL if anything looks unfamiliar).
Listing 5. BCEL glue class construction
/** Parameter types for call with single int value. */
private static final Type[] INT_ARGS = { Type.INT };
/** Utility method for adding constructed method to class. */
private static void addMethod(MethodGen mgen, ClassGen cgen) {
mgen.setMaxStack();
mgen.setMaxLocals();
InstructionList ilist = mgen.getInstructionList();
Method method = mgen.getMethod();
ilist.dispose();
cgen.addMethod(method);
}
protected byte[] createAccess(Class tclas,
java.lang.reflect.Method gmeth, java.lang.reflect.Method smeth,
String cname) {
// build generators for the new class
String tname = tclas.getName();
ClassGen cgen = new ClassGen(cname, "java.lang.Object",
cname + ".java", Constants.ACC_PUBLIC,
new String[] { "IAccess" });
InstructionFactory ifact = new InstructionFactory(cgen);
ConstantPoolGen pgen = cgen.getConstantPool();
//. add target object field to class
FieldGen fgen = new FieldGen(Constants.ACC_PRIVATE,
new ObjectType(tname), "m_target", pgen);
cgen.addField(fgen.getField());
int findex = pgen.addFieldref(cname, "m_target",
Utility.getSignature(tname));
// create instruction list for default constructor
InstructionList ilist = new InstructionList();
ilist.append(InstructionConstants.ALOAD_0);
ilist.append(ifact.createInvoke("java.lang.Object", "<init>",
Type.VOID, Type.NO_ARGS, Constants.INVOKESPECIAL));
ilist.append(InstructionFactory.createReturn(Type.VOID));
// add public default constructor method to class
MethodGen mgen = new MethodGen(Constants.ACC_PUBLIC, Type.VOID,
Type.NO_ARGS, null, "<init>", cname, ilist, pgen);
addMethod(mgen, cgen);
// create instruction list for setTarget method
ilist = new InstructionList();
ilist.append(InstructionConstants.ALOAD_0);
ilist.append(InstructionConstants.ALOAD_1);
ilist.append(new CHECKCAST(pgen.addClass(tname)));
ilist.append(new PUTFIELD(findex));
ilist.append(InstructionConstants.RETURN);
// add public setTarget method
mgen = new MethodGen(Constants.ACC_PUBLIC, Type.VOID,
new Type[] { Type.OBJECT }, null, "setTarget", cname,
ilist, pgen);
addMethod(mgen, cgen);
// create instruction list for getValue method
ilist = new InstructionList();
ilist.append(InstructionConstants.ALOAD_0);
ilist.append(new GETFIELD(findex));
ilist.append(ifact.createInvoke(tname, gmeth.getName(),
Type.INT, Type.NO_ARGS, Constants.INVOKEVIRTUAL));
ilist.append(InstructionConstants.IRETURN);
// add public getValue method
mgen = new MethodGen(Constants.ACC_PUBLIC, Type.INT,
Type.NO_ARGS, null, "getValue", cname, ilist, pgen);
addMethod(mgen, cgen);
// create instruction list for setValue method
ilist = new InstructionList();
ilist.append(InstructionConstants.ALOAD_0);
ilist.append(new GETFIELD(findex));
ilist.append(InstructionConstants.ILOAD_1);
ilist.append(ifact.createInvoke(tname, smeth.getName(),
Type.VOID, INT_ARGS, Constants.INVOKEVIRTUAL));
ilist.append(InstructionConstants.RETURN);
// add public setValue method
mgen = new MethodGen(Constants.ACC_PUBLIC, Type.VOID,
INT_ARGS, null, "setValue", cname, ilist, pgen);
addMethod(mgen, cgen);
// return bytecode of completed class
return cgen.getJavaClass().getBytes();
}
|
Now that I've got code for both Javassist and BCEL versions of the method construction, I can try them out to see how well they work. My original reason for generating code at runtime was to replace reflection with something faster, so it would be good to include a performance comparison to see how well I've succeeded. Just to make it interesting, I'll also look at the time it takes to construct the glue class with each of the frameworks.
Listing 6 shows the main parts of the test code I'll use to check out the
performance. The runReflection() method runs the
reflection part of the test, runAccess() runs the
direct access part, and run() controls
the whole process (including printing the timing results). Both runReflection()
and runAccess() take the number of loops to be executed as a parameter, which is in
turn passed in from the command line (using code not shown in this listing, but
included in the download). The DirectLoader class (at
the end of Listing 6) just provides an easy way of loading the generated
classes.
Listing 6. Performance test code
/** Run timed loop using reflection for access to value. */
private int runReflection(int num, Method gmeth, Method smeth,
Object obj) {
int value = 0;
try {
Object[] gargs = new Object[0];
Object[] sargs = new Object[1];
for (int i = 0; i < num; i++) {
// messy usage of Integer values required in loop
Object result = gmeth.invoke(obj, gargs);
value = ((Integer)result).intValue() + 1;
sargs[0] = new Integer(value);
smeth.invoke(obj, sargs);
}
} catch (Exception ex) {
ex.printStackTrace(System.err);
System.exit(1);
}
return value;
}
/** Run timed loop using generated class for access to value. */
private int runAccess(int num, IAccess access, Object obj) {
access.setTarget(obj);
int value = 0;
for (int i = 0; i < num; i++) {
value = access.getValue() + 1;
access.setValue(value);
}
return value;
}
public void run(String name, int count) throws Exception {
// get instance and access methods
HolderBean bean = new HolderBean();
String pname = name;
char lead = pname.charAt(0);
pname = Character.toUpperCase(lead) + pname.substring(1);
Method gmeth = null;
Method smeth = null;
try {
gmeth = HolderBean.class.getDeclaredMethod("get" + pname,
new Class[0]);
smeth = HolderBean.class.getDeclaredMethod("set" + pname,
new Class[] { int.class });
} catch (Exception ex) {
System.err.println("No methods found for property " + pname);
ex.printStackTrace(System.err);
return;
}
// create the access class as a byte array
long base = System.currentTimeMillis();
String cname = "IAccess$impl_HolderBean_" + gmeth.getName() +
"_" + smeth.getName();
byte[] bytes = createAccess(HolderBean.class, gmeth, smeth, cname);
// load and construct an instance of the class
Class clas = s_classLoader.load(cname, bytes);
IAccess access = null;
try {
access = (IAccess)clas.newInstance();
} catch (IllegalAccessException ex) {
ex.printStackTrace(System.err);
System.exit(1);
} catch (InstantiationException ex) {
ex.printStackTrace(System.err);
System.exit(1);
}
System.out.println("Generate and load time of " +
(System.currentTimeMillis()-base) + " ms.");
// run the timing comparison
long start = System.currentTimeMillis();
int result = runReflection(count, gmeth, smeth, bean);
long time = System.currentTimeMillis() - start;
System.out.println("Reflection took " + time +
" ms. with result " + result + " (" + bean.getValue1() +
", " + bean.getValue2() + ")");
bean.setValue1(0);
bean.setValue2(0);
start = System.currentTimeMillis();
result = runAccess(count, access, bean);
time = System.currentTimeMillis() - start;
System.out.println("Generated took " + time +
" ms. with result " + result + " (" + bean.getValue1() +
", " + bean.getValue2() + ")");
}
/** Simple-minded loader for constructed classes. */
protected static class DirectLoader extends SecureClassLoader
{
protected DirectLoader() {
super(TimeCalls.class.getClassLoader());
}
protected Class load(String name, byte[] data) {
return super.defineClass(name, data, 0, data.length);
}
}
|
For a simple timing test, I call the run() method
twice, once for each of the properties in the Listing 1 HolderBean
class. Running the two test passes is important for a reasonably fair test -- the first pass through
the code is going to load all the necessary classes, which adds a lot of
overhead to both the Javassist and BCEL class generation process. This overhead
isn't required on the second pass, though, giving you a better estimate of
how long the class generation would require when used within a real system.
Here's a sample of the generated output when the test is executed:
[dennis]$$ java -cp .:bcel.jar BCELCalls 2000 Generate and load time of 409 ms. Reflection took 61 ms. with result 2000 (2000, 0) Generated took 2 ms. with result 2000 (2000, 0) Generate and load time of 1 ms. Reflection took 13 ms. with result 2000 (0, 2000) Generated took 2 ms. with result 2000 (0, 2000) |
Figure 1 shows the results of this timing test when called with loop counts ranging from 2K to 512K (tests run on an Athlon 2200+ XP system running Mandrake Linux 9.1, using Sun's 1.4.2 JVM). Here I've included both the reflection and generated code times for the second property within each test run (so the pair of times when using the Javassist code generation are first, followed by the same pair of times when using the BCEL code generation). The execution times are about the same regardless of whether Javassist or BCEL is used to generate the glue classes, which is what I'd expect to see -- but it's always good to have confirmation!
Figure 1. Reflection vs. generated code speed (time in milliseconds)

As you can see from Figure 1, the generated code executes much faster than reflection in every case. The speed advantage for generated code increases as the number of loops goes up, starting out at roughly 5:1 with 2K loops and going up to about 24:1 with 512K loops. Constructing and loading the first glue class took about 320 milliseconds (ms) for Javassist, and 370 ms for BCEL, while constructing the second glue class took only about 4 ms for Javassist and 2 ms for BCEL (though the clock resolution is only 1 ms, so these times are very rough). If you combine these times, you'll see that even for the 2K loop case generating a class is going to give better performance overall than using reflection (with a total execution time of about 4 ms to 6 ms, versus about 14 ms for reflection).
In actuality, the situation is even more biased in favor of the generated code than this chart seems to indicate. When I tried going as low as 25 loops, the reflection code still took 6 ms to 7 ms to execute, while the generated code was too fast to register. The time taken by reflection for relatively small loop counts appears to reflect some optimizations going on within the JVM when a threshold is reached; if I lowered the loop count below about 20, the reflection code also became too fast to register.
Now you've seen the kind of performance that runtime classworking can deliver for your applications. Keep it in mind the next time you're facing an intractable performance optimization problem -- it may just be the magic bullet that can save you a major redesign. Classworking is good for more than just performance, though. It's also a uniquely flexible approach to tailoring your application to runtime requirements. Even if you never have cause to use it in your code, I think it's one of the features of Java that keeps programming fun and interesting.
This venture into a real-world application of classworking wraps up the series on Java programming dynamics. But don't despair -- you'll soon get the chance to sample some other classworking applications at the developerWorks buffet when I present some of the tools that have been built around Java bytecode manipulation. First up will be an article on a pair of test tools straight out of Mother Goose.
| Name | Size | Download method |
|---|---|---|
| j-dyn0610.zip | 764KB | HTTP |
Information about download methods
- Learn more about the Java bytecode design in "Java bytecode: Understanding
bytecode makes you a better programmer" (developerWorks, July 2001) by Peter Haggar.
- Read "Improve
modularity with aspect-oriented programming" (developerWorks, January 2002) by Nicholas Lesiecki to learn more about aspect-oriented programming.
- For an excellent reference to the JVM architecture and instruction set, see
Inside the Java Virtual Machine, by Bill Venners (Artima Software, Inc., 2004). You can view some sample chapters online
to get a look at it before you purchase.
- You can purchase
or view the official Java Virtual Machine Specification online for the definitive word on
all aspects of JVM operation.
- The open source Jikes Project
provides a very fast and highly compliant compiler for the Java programming
language. Use it to generate your bytecode the old fashioned way -- from Java
source code.
- Browse for books on these and other technical topics.
- You'll find hundreds of articles about every aspect of Java programming
in the developerWorks Java technology zone.

Dennis Sosnoski is the founder and lead consultant of Seattle-area Java consulting company Sosnoski Software Solutions, Inc., specialists in J2EE, XML, and Web services support. His professional software development experience spans over 30 years, with the last several years focused on server-side Java technologies. Dennis is a frequent speaker on XML and Java technologies at conferences nationwide, and chairs the Seattle Java-XML SIG and Eclipse SIG. Contact Dennis at dms@sosnoski.com.
