Java programming dynamics, Part 8: Replacing reflection with code generation

Runtime code generation offers a way of replacing reflection with direct access for top performance

Earlier in this article series, you learned how reflection performance is many times slower than direct access, and then learned about classworking with Javassist and the Apache Byte Code Engineering Library (BCEL). Java consultant Dennis Sosnoski wraps up his Java programming dynamics series by demonstrating how you can use runtime classworking to replace reflection code with generated code that runs at full speed ahead.

Share:

Dennis Sosnoski, President, Sosnoski Software Solutions, Inc.

Photo of Dennis SosnoskiDennis 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.



10 June 2004

Also available in Japanese Vietnamese

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.

Reflections on performance

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.

Setting the stage

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

Don't miss the rest of this series

Part 1, "Classes and class loading"(April 2003)

Part 2, "Introducing reflection" (June 2003)

Part 3, "Applied reflection" (July 2003)

Part 4, "Class transformation with Javassist" (September 2003)

Part 5, "Transforming classes on-the-fly" (February 2004)

Part 6, "Aspect-oriented changes with Javassist" (March 2004)

Part 7, "Bytecode engineering with BCEL" (April 2004)


Building a glue class

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.

Ask the expert: Dennis Sosnoski on JVM and bytecode issues

For comments or questions about the material covered in this article series, as well as anything else that pertains to Java bytecode, the Java binary class format, or general JVM issues, visit the JVM and Bytecode discussion forum, moderated by Dennis Sosnoski.

Generating with Javassist

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 with BCEL

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();
}

Performance check

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)
Reflection vs. generated code speed

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.


Speeding you on your way

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.


Download

DescriptionNameSize
Code samplej-dyn0610.zip764KB

Resources

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Java technology on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java technology
ArticleID=10951
ArticleTitle=Java programming dynamics, Part 8: Replacing reflection with code generation
publish-date=06102004