-
Bug
-
Resolution: Unresolved
-
P3
-
None
-
11.0.10, 11.0.11, 16
-
None
There is a deadlock when loading a native library on one thread, while simultaneously attempting to load a class from a signed jar on another thread.
The following repo contains a reproducer and more detailed explanation:
https://github.com/ChrisHegarty/deadlock
Summary of the reproducer and issue:
The Foo and FooPrime classes themselves are uninteresting. What is interesting is that they live in the signed foo.jar. The sole purpose of the Foo and FooPrime classes is to have two different types that can be loadable from a signed jar on the class path (other than that of the other test classes/jars).
The Bar class contains a class initializer which loads the native libbar library. It lives in bar.jar The JNI_OnLoad of libbar tries to find/load the FooPrime class.
The Main class lives in main.jar and is the orchestrator. The Main::main entry point method starts a race (on foo.jar); one thread, Thread-A, loading the FooPrime class transitively through the initialization of the Bar class, and another thread, Thread-B, loading the Foo class directly.
The class path is a list of archives (and directories) that are searched sequentially by the application classloader, i.e. order is significant. In this particular case, bar.jar is before foo.jar - which is large part of the issue.
The deadlock occurs in the classloader when:
Thread A tries to lock the monitor on the backing JarFile for foo.jar, while holding the monitor for the JVM-wide Runtime object, while
Thread B tries to lock the monitor for the JVM-wide Runtime object, while holding the monitor on the backing JarFile for foo.jar.
We know why Thread-A is doing what it is doing (since that is clear from the test code), but Thread-B is a little more mysterious. Thread-B is verifying the signature of the signed jar file foo.jar. To do this, Thread-B triggers the initialization of the JCE framework, which in turn fires up service-loader to search the JDK's installed providers. This search can result in an attempt to load a native library (associated with the provider implementation itself, like say SunEC, or just some other implementation detail of the classloader ).
---
The current workaround for such issues is to preload classes referred to by JNI_OnLoad, before calling JNI_OnLoad. This should circumvent class loading from occurring during the execution of JNI_OnLoad.
An example of an issue that arises from this loader limitation is described here: https://github.com/netty/netty/issues/11209
The following repo contains a reproducer and more detailed explanation:
https://github.com/ChrisHegarty/deadlock
Summary of the reproducer and issue:
The Foo and FooPrime classes themselves are uninteresting. What is interesting is that they live in the signed foo.jar. The sole purpose of the Foo and FooPrime classes is to have two different types that can be loadable from a signed jar on the class path (other than that of the other test classes/jars).
The Bar class contains a class initializer which loads the native libbar library. It lives in bar.jar The JNI_OnLoad of libbar tries to find/load the FooPrime class.
The Main class lives in main.jar and is the orchestrator. The Main::main entry point method starts a race (on foo.jar); one thread, Thread-A, loading the FooPrime class transitively through the initialization of the Bar class, and another thread, Thread-B, loading the Foo class directly.
The class path is a list of archives (and directories) that are searched sequentially by the application classloader, i.e. order is significant. In this particular case, bar.jar is before foo.jar - which is large part of the issue.
The deadlock occurs in the classloader when:
Thread A tries to lock the monitor on the backing JarFile for foo.jar, while holding the monitor for the JVM-wide Runtime object, while
Thread B tries to lock the monitor for the JVM-wide Runtime object, while holding the monitor on the backing JarFile for foo.jar.
We know why Thread-A is doing what it is doing (since that is clear from the test code), but Thread-B is a little more mysterious. Thread-B is verifying the signature of the signed jar file foo.jar. To do this, Thread-B triggers the initialization of the JCE framework, which in turn fires up service-loader to search the JDK's installed providers. This search can result in an attempt to load a native library (associated with the provider implementation itself, like say SunEC, or just some other implementation detail of the classloader ).
---
The current workaround for such issues is to preload classes referred to by JNI_OnLoad, before calling JNI_OnLoad. This should circumvent class loading from occurring during the execution of JNI_OnLoad.
An example of an issue that arises from this loader limitation is described here: https://github.com/netty/netty/issues/11209