Classloader Deep-Cloning without Serialization

Background

In PowerMock we’re using a custom classloader to byte-code manipulate classes that are normally not mockable to make them mockable. But when running a test case there may be some cases when the user needs to byte-code manipulate a certain class (X) in the first test method but needs to have the class unmodified in the second. For example you may want to remove the static initializer of class X in testA but in testB you want the static initializer intact. There’s no standard way for a Java classloader to reload a class that has already been loaded so what to do? The solution is to execute that particular test method in a new classloader. Using this approach we can simply load X again and this time we won’t modify it. In PowerMock we’ve also implemented a custom JUnit runner which allows us to execute certain test-methods in another classloader. Without going into too much detail the problem is that we need to execute some test methods in a new classloader as well as being able to supply and receive state between our classloaders. The end user shouldn’t be aware that the unit test they’re running is actually chunked up into several different JUnit test cases executed in many different classloaders.

Requirements

Our goal was to be able to execute any block of code in any given classloader without using any byte-code manipulation. We can’t make any assumptions about the class such as it being serializable or having a default constructor. We also need the outcome of this code-block to be used by the original/invoking classloader.

Solution

The solution was to implement a ClassLoaderExecutor which takes a Runnable or Callable and executes this code-block in any classloader. In the case of Callable the result from the callable invocation is cloned back to the original classloader calling the method. Let’s look at an example.

First we define the code-block to be executed in the other classloader:

Then we simply execute this block using the ClassLoaderExecutor:

How does it work?

The first thing that happens when execute is called is that almost the entire object graph of the code-block is deep-cloned into the target classloader (myClassloader in the example above). The objects that are not cloned are those whose classes reside in the rt.jar which are always loaded by the bootstrap classloader. These objects are (in the simple cases) only referred to by a cloned object. Since we cannot make any assumptions about the class structure or hierarchy we cannot simply do deep-cloning using standard Java serialization. Neither can we clone the objects using simple reflection because how do we know which constructor to invoke when instantiating the object using the target classloader? Essentially the trick is to instantiate the class without executing any constructor at all. When this is done we iterate over all fields and instantiate and copy their values recursively. But how can you instantiate a class without invoking a constructor? Unfortunately there’s no standard way of doing this. In a modern Sun JVM you can use sun.reflect.ReflectionFactory but it’s not guaranteed that other JVM’s have implemented this class. To make it more compatible we make use of the excellent Objenesis framework whose sole purpose is to accommodate this across multiple JVM’s from different vendors. When all state has been copied from the source to the target classloader using this approach the result of the callable operation (myResult in the example above) is cloned back from the target to the source classloader. This means that the result is directly usable from the source classloader after the execute method has finished.

Conclusion

This article has demonstrated one approach of executing a block of code in any given classloader using deep-cloning without serialization. There are many corner cases which we haven’t dealt with here such as dealing with arrays, lists, enum, static final fields, object references etc that the ClassLoaderExecutor also supports. An alternative may be to use XStream as deep-cloner to serialize the object graph to XML and de-serialize it back to the target classloader but the approach suggested here should potentially be faster.

This Post Has 3 Comments

  1. Hi Johan

    Nice work on the Powermock tool. A 3.0 version of EasyMock is released. Do you have any plans of upgrading Powermock to that version?

    Regards
    Thomas

  2. Done in version 1.3.9 :)

Leave a Reply

Close Menu