Custom Classloader: Issues with the Enhancer Agent

#1

In my use case I generate some classes dynamically and load them with a custom classloader. The classloader is very standard and does something like this (simplified):

public class MemoryClassLoader extends ClassLoader {

  @Override
  public Class<?> findClass(String name) throws ClassNotFoundException {
   javaClass = getGeneratedClass(name);
   if(javaClass != null)
   {
    byte[] bytes = javaClass.getBytes();
    return defineClass(name, bytes, 0, bytes.length);
   }else{
    throw new ClassNotFoundException();
   }
  }
}

This works great without the Enhancer Agent, but once the Agent is enabled it is causing issues and I get errors like this:

Java.lang.LinkageError: loader (instance of  rsl/core/classloaders/MemoryClassLoader): attempted  duplicate class definition for name: "rsl/models/shared/selectors/ImageSelector"
        at java.lang.ClassLoader.defineClass1(Native Method)
        at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
        at java.lang.ClassLoader.defineClass(ClassLoader.java:642)

I assume this is because the Agent intercepts my call to defineClass(), makes the enhancement to the class and loads the modified version in memory first. However, the main issue is that the Agent seems to call back to my classloader (loadClass()/findClass()/...) as part of the enhancement process, resulting in a recursive call to my custom findClass() method. This means that the LinkageError is thrown before my first call to MemoryClassLoader.findClass() even returns, making it hard to implement a workaround. A simplified call stack to illustrate what I mean:

MYLOADER: MemoryClassLoader.loadAllGeneratedClasses()
{
  MYLOADER: MemoryClassLoader.findClass("myGeneratedClass")
  {
    MYLOADER: defineClass("myGeneratedClass")
    {
     OBJECTDB_AGENT: performEnhancementProcess()
     {
      OBJECTDB_AGENT: MemoryClassLoader.findClass("myGeneratedClass")
      {
       MYLOADER: defineClass("myGeneratedClass")
       {
        ERROR, attempted  duplicate class definition for name: "myGeneratedClass"
       }
      }
     }
    }
  }
}

The most obvious solution would be that my findClass() method checks if the class is already loaded (e.g. by the agent) to prevent calling defineClass() twice, but the standard ways for searching classes (e.g. Class.forName() or the Reflections library) claim that the conflicting classes don't exist anywhere, so I'm not sure what to do now. It's weird because I can't seem to detect the classes on the classpath even at the moment when the LinkageError is thrown.

So my questions are:

  • Can I detect if the Agent has already loaded the modified class so that I can return that instead?
  • Does the classloader used by the enhancement agent respect the classloading hierarchy? E.g. Can I delegate the class lookup to the agent's classloader to get the new modified version and return that?
  • Any other ideas on how to approach this issue?

Thank you for your time.

#2

> I assume this is because the Agent intercepts my call to defineClass(), makes the enhancement to the class and loads the modified version in memory first.

Not exactly. The enhancer loads the class before enhancement, as currently analysis of the class for enhancement is done  using the Java reflection API, so the class has to be loaded before enhancement. Because of possible dependency on other classes the user's class loader is used for this purpose.

This should be changed in future ObjectDB versions as it has many drawbacks.

> Can I detect if the Agent has already loaded the modified class so that I can return that instead?

Assuming loadClass of your default class loader (e.g. the thread's context class loader) is called you may be able to intercept the call by implementing your own loadClass (note: it may be a different class loader than the one that you implement for the dynamic classes).

> Does the classloader used by the enhancement agent respect the classloading hierarchy? E.g. Can I delegate the class lookup to the agent's classloader to get the new modified version and return that?

As explained above the problem is that the class before enhancement is actually loaded.

> Any other ideas on how to approach this issue?

You may be able to use a dummy class loader just to enable the Enhancer to load classes before enhancement, but avoid using that class loader for anything else.

ObjectDB Support

Reply