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.
System.load
delegates to the package-private methodRuntime.load0
.Runtime.load0
delegates to the package-private static methodClassLoader.loadLibrary
.ClassLoader.loadLibrary
delegates to the private static methodClassLoader.loadLibrary0
.ClassLoader.loadLibrary0
creates an object of the package-private inner classClassLoader.NativeLibrary
and delegates to itsload
method.ClassLoader.NativeLibrary.load
is a native method, which delegates to the functionJVM_LoadLibrary
.JVM_LoadLibrary
delegates toos::dll_load
.os::dll_load
is system dependent.- The Linux variant of
os::dll_load
delegates to thedlopen
system call, giving theRTLD_LAZY
option. - The Linux variant of the POSIX
dlopen
system call hasRTLD_LOCAL
behaviour by default, so the shared library is loaded withRTLD_LOCAL
semantics. 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.- 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?
- 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. - That wrapper function makes use of a
methodHandle
C++ object, getting the address of the JNI function from either themethodHandle::critical_native_function()
ormethodHandle::native_function()
, as appropriate. - The address of the JNI function is recorded in the
methodHandle
by a call tomethodHandle::set_native_function
fromNativeLookup::lookup
. NativeLookup::lookup
delegates, indirectly, toNativeLookup::lookup_style
NativeLookup::lookup_style
delegates to the Java package-private static methodClassLoader.findNative
.ClassLoader.findNative
iterates through the list (ClassLoader.nativeLibraries
) ofClassLoader.NativeLibrary
objects set up byClassLoader.loadLibrary0
, in the order that the libraries were loaded. For each library, it delegates toNativeLibrary.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.NativeLibrary.find
is a native method. It simply delegates toJVM_FindLibraryEntry
.JVM_FindLibraryEntry
delegates to the system dependent methodos::dll_lookup
.- The Linux implementation of
os::dll_lookup
delegates to thedlsym
system call to lookup the address of the function in the shared library. - 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
Load Resource from Anywhere in Classpath
How to Create a Topic in Kafka from the Ide Using API
Java, "Variable Name" Cannot Be Resolved to a Variable
How to Change the Shape of a Jtabbedpane Tab
What Does an Exclamation Mark Mean in Java
Multiple Java Versions Running Concurrently Under Windows
How to Create an Object of an Interface
Convert String to Calendar Object in Java
Access to Private Inherited Fields via Reflection in Java
Why Converting from Float to Double Changes the Value
How to Connect to a SQL Server 2008 Database Using Jdbc
I Found JPA, or Alike, Don't Encourage Dao Pattern
How to Test to See If a Double Is Equal to Nan
How to Combine Two Lists into a Map (Java)