Java - How to Load Different Versions of the Same Class

Java - how to load different versions of the same class?

You're going the right way. You must take some things into account.

The normal thing is classes that exist in parent classloaders are used. So if you want two versions those classes must not be there.

But if you want to interact you can use reflection, or even better a common interface. So I'll do this:

common.jar:
BaseInterface

v1.jar:
SomeImplementation implements BaseInterface

v2.jar:
OtherImplementation implements BaseInterface

command-line:
java -classpath common.jar YourMainClass
// you don't put v1 nor v2 into the parent classloader classpath

Then in your program:

loader1 = new URLClassLoader(new URL[] {new File("v1.jar").toURL()}, Thread.currentThread().getContextClassLoader());
loader2 = new URLClassLoader(new URL[] {new File("v2.jar").toURL()}, Thread.currentThread().getContextClassLoader());

Class<?> c1 = loader1.loadClass("com.abc.Hello");
Class<?> c2 = loader2.loadClass("com.abc.Hello");

BaseInterface i1 = (BaseInterface) c1.newInstance();
BaseInterface i2 = (BaseInterface) c2.newInstance();

Loading multiple versions of Java classes that use native code

Examining the Open JDK 7 implementation, it seems that, yes, loading multiple versions of Java classes that use native code will work:

Library Loading

Crucial information is, how does System.load behave? The implementation of that method will be system dependent, but the semantics of the various implementations should be the same.

  1. System.load delegates to the package-private method Runtime.load0.
  2. Runtime.load0 delegates to the package-private static method ClassLoader.loadLibrary.
  3. ClassLoader.loadLibrary delegates to the private static method ClassLoader.loadLibrary0.
  4. ClassLoader.loadLibrary0 creates an object of the package-private inner class ClassLoader.NativeLibrary and delegates to its load method.
  5. ClassLoader.NativeLibrary.load is a native method, which delegates to the function JVM_LoadLibrary.
  6. JVM_LoadLibrary delegates to os::dll_load.
  7. os::dll_load is system dependent.
  8. The Linux variant of os::dll_load delegates to the dlopen system call, giving the RTLD_LAZY option.
  9. The Linux variant of the POSIX dlopen system call has RTLD_LOCAL behaviour by default, so the shared library is loaded with RTLD_LOCAL semantics.
  10. RTLD_LOCAL semantics are that the symbols in the loaded library are not made available for (automatic) symbol resolution of subsequently loaded libraries. That is, the symbols do not enter the global namespace, and different libraries may define the same symbols without generating conflicts. The shared libraries could even have identical content without problems.
  11. Hence it does not matter if different shared libraries, loaded by different class loaders, define the same symbols (have the same names of extern functions for native methods): the JRE and JVM together avoid name clashes.

Native Function Lookup

That ensures that the multiple versions of the shared libraries do not generate name conflicts. But how does OpenJDK ensure that the correct JNI code is used for the native method calls?

  1. The procedure followed by the JVM to call a native method is rather lengthy, but it is all contained within one function, SharedRuntime::generate_native_wrapper. Ultimately, however, that needs to know the address of the JNI function to be called.
  2. That wrapper function makes use of a methodHandle C++ object, getting the address of the JNI function from either the methodHandle::critical_native_function() or methodHandle::native_function(), as appropriate.
  3. The address of the JNI function is recorded in the methodHandle by a call to methodHandle::set_native_function from NativeLookup::lookup.
  4. NativeLookup::lookup delegates, indirectly, to NativeLookup::lookup_style
  5. NativeLookup::lookup_style delegates to the Java package-private static method ClassLoader.findNative.
  6. ClassLoader.findNative iterates through the list (ClassLoader.nativeLibraries) of ClassLoader.NativeLibrary objects set up by ClassLoader.loadLibrary0, in the order that the libraries were loaded. For each library, it delegates to NativeLibrary.find to try to find the native method of interest. Although this list of objects is not public, the JNI specification requires that the JVM "maintains a list of loaded native libraries for each class loader", so all implementations must have something similar to this list.
  7. NativeLibrary.find is a native method. It simply delegates to JVM_FindLibraryEntry.
  8. JVM_FindLibraryEntry delegates to the system dependent method os::dll_lookup.
  9. The Linux implementation of os::dll_lookup delegates to the dlsym system call to lookup the address of the function in the shared library.
  10. Because each class-loader maintains its own list of loaded libraries, it is guaranteed that the JNI code called for a native method will be the correct version, even if a different class loader loads a different version of the shared library.

Java: Dynamically Load Multiple Versions of Same Class

Based on your answer to my question, it seems you want to define a game interface and then plug in any number of AI implementations, probably configured from a .properties file. This is fairly standard use of an API interface.

You define an EngineInterface providing a method that accepts game state and returns the move. Then you define multiple classes that all implement EngineInterface. Your driver reads a property file to get the names of the implementation classes, instantiates them with Class.forName() and stores them in a list and/or map. Then when the driver gets requests it invokes each implementation in turn and keeps track of the results.

how to load multiple version of same class using dynamic class loading in java

The following is a rough sketch of the classloader you need

class HelloWorldClassLoader extends ClassLoader {

@Override
public Class loadClass(String name) throws ClassNotFoundException {
if (!"MyClass".equals(name)) return super.loadClass(name);
byte[] bb=ByteStreams.toByteArray(
getResourceAsStream(name.replace('.','/')+".class"));
return defineClass(name,bb,0,bb.length);
}
}

To use it, do

new HelloWorldClassLoader().loadClass("MyClass");

Java, Classpath, Classloading = Multiple Versions of the same jar/project

Classloader related problems are a quite complex matter.
You should in any case keep in mind some facts:

  • Classloaders in an application are usually more than a single one. The bootstrap class loader delegates to the appropriate. When you instantiate a new class the more specific classloader is invoked. If it does not find a reference to the class you are trying to load, it delegates to its parent, and so on, until you get to the bootstrap class loader. If none of them find a reference to the class you are trying to load you get a ClassNotFoundException.

  • If you have two classes with the same binary name, searchable by the same classloader, and you want to know which one of them you are loading, you can only inspect the way that specific classloader tries to resolve a class name.

  • According to the java language specification, there is not a uniqueness constraint for a class binary name, but as far as I can see, it should be unique for each classloader.

I can figure out a way to load two classes with the same binary name, and it involves to have them loaded (and all their dependencies) by two different classloaders overriding default behaviour.
A rough example:

    ClassLoader loaderA = new MyClassLoader(libPathOne);
ClassLoader loaderB = new MyClassLoader(libPathTwo);
Object1 obj1 = loaderA.loadClass("first.class.binary.name", true)
Object2 obj2 = loaderB.loadClass("second.class.binary.name", true);

I always found classloader customization a tricky task. I'd rather suggest to avoid multiple
incompatible dependencies if possible.



Related Topics



Leave a reply



Submit