JDK8: Unloading native JNI library
Vemund Ostgaard
vemund.ostgaard at oracle.com
Thu Aug 24 12:37:49 UTC 2017
Hello,
I am struggling with a problem related to unloading (and reloading) a
native JNI library that has been loaded with System.load(). I have been
referred to this mailing list as the right place to ask, so seeking your
advice on the issue.
My goal was to load a JNI library from a JVM process and be able to
reload an updated copy of the same library later without restarting the
JVM. The library might be loaded from the exact same path as before or
from a different path.
The testing I have done (more details below) indicates to me that the
first JNI library I load with System.load() does not get unloaded
completely when its loading classloader is garbage collected, subsequent
attempts to load a library from the same path indicates that the native
library kept the state it had from the first loaded copy.
Surprisingly though, any other copy of the native library that is loaded
from other file system locations than the first path are apparently
unloaded when their loading classloader is garbage collected, and
subsequent attempts to load from those paths indicate that those
libraries do not keep their state.
So, I am curious whether there is something special about the first time
System.load() is used from application code in a new JVM, and whether
there is some way I can avoid that behavior.
Details about my testing:
I have added these methods to my native JNI library:
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved)
{
counter++;
printf("JNI_OnLoad called: %d\n", counter);
(void)jvm;
(void)reserved;
return JNI_VERSION_1_4;
}
JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *jvm, void *reserved)
{
printf( "JNI_OnUnload called: %d\n", counter);
(void)jvm;
(void)reserved;
return;
}
The counter is a global variable in the same file, and it is initialized
to 0. The plan was to see if the counter was initialized to 0 again
after the library is unloaded and loaded again, and also to see that
these methods were called at all. I also recompiled the native library
in two other versions with the counter initialized to 100 and 200
respectively, to be able to tell the difference between different copies
of the library being loaded/unloaded.
I wrote a small java class that calls System.load() to load my native
JNI library, and a small custom classloader that extends URL
classloader. To both of these classes I added overrides for finalize()
just to see it being called and printing out a unique ID to know what
copy of each class was being finalized.
The third Java class is just a main() method that calls a test() method
with the test logic using the other two classes to test loading and
unloading of the native library. When the test() method returns to the
main() method, the classloader object goes out of scope and I call
System.gc() a few times to get everything garbage collected. The test()
method also returns a WeakReference() to the classloader object so I can
call WeakReference.get() before and after the System.gc() calls to
verify that this becomes null. I interpret the WeakReference becoming
null as a sign that my classloader is getting garbage collected.
My testing shows that in all cases:
- JNI_OnLoad() and JNI_OnUnload() gets called as expected
- The finalizer() methods for my two classes are called as expected
- The WeakReference.get() for my custom classloader returns null after a
few System.gc() calls and sleeping a couple seconds total.
As already mentioned, the first JNI library loaded keeps its state, the
counter printed from JNI_OnLoad() and JNI_OnUnload() keeps increasing
for every repeated load+unload from that file location, while for all
other libraries loaded from other file locations, the state is
apparently reset. This holds true also if using multiple custom
classloaders or identical copies of the JNI library in different
locations. The key factor seems to only be loading from the same file
location as used the first time.
I can't see that my code behaves any different for the multiple times I
try to load and unload the JNI libraries, so I am wondering if there is
some JNI bug or known behavior that explains what I see?
Thank you,
Vemund
More information about the core-libs-dev
mailing list