JDK-8188052 : JNI FindClass needs to specify the class loading context used for library lifecycle hooks
  • Type: Bug
  • Component: hotspot
  • Sub-Component: runtime
  • Priority: P3
  • Status: Resolved
  • Resolution: Fixed
  • Submitted: 2017-09-27
  • Updated: 2018-03-22
  • Resolved: 2017-10-10
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 10
10 b31Fixed
Related Reports
Blocks :  
CSR :  
Description
The current implementation of FindClass uses the class loader associated with the native library as the context to load a class when it is invoked from both JNI_OnLoad and JNI_OnUnload.

The spec of JNI_OnUnload [1] specifies that JNI_OnUnload function is called in an unknown context (such as from a finalizer), the programmer should be conservative on using Java VM services, and refrain from arbitrary Java call-backs.

FindClass may resurrect the class loader that is being finalized but the native library has already been unloaded.

The spec of JNI FindClass [2] says the following:

FindClass locates the class loader associated with the current native method; that is, the class loader of the class that declared the native method. 
:
when FindClass is called through the Invocation Interface, there is no current native method or its associated class loader. In that case, the result of ClassLoader.getSystemClassLoader is used.

There are two issues here:
1. When FindClass is called from JNI_OnUnload, the context should be unknown whereas the implementation uses the class loader being unloaded.

2. The spec of FindClass needs clarification what class loader it uses when the function is called from JNI_OnUnload.

I propose to change the spec that FindClass if called from JNI_OnUnload will behave as if there is no associated class loader when called through the Invocation Interface - i.e. use ClassLoader.getSystemClassLoader as the context class loader.   The new behavior will ensure the class loader being unloaded will not be resurrected and native library should not be able to find a class that is defined by a class loader being unloaded.  If an native unload hook has to access any class defined by a reachable parent class loader, it should cache the class prior to unload.

[1] https://docs.oracle.com/javase/9/docs/specs/jni/invocation.html#jni_onunload
[2] https://docs.oracle.com/javase/9/docs/specs/jni/functions.html#findclass
Comments
JNI_OnLoad was introduced in JDK 1.2 [1] and the spec was updated in JDK 1.4 [2]. It was designed for a native library to return the minimum JNI version that it requires to access new JNI functionality. If the VM does not support the version number returned by JNI_OnLoad, the native library cannot be loaded so that mismatch can be found immediately. JNI version should be bumped for this proposed `FindClass` spec change. It is a behavioral incompatible change. The native library with JNI_OnLoad returning JNI_VERSION_9 will be loaded successfully (since VM supports the given version number). The native library can be modified to depend on the version returned by GetVersion for different code path. However I would strongly recommend to fix the native library not to call `FindClass` to find a class defined by a class loader being garbage collected even running on JDK 9 and caching a global/weak JNI ref of jclass should be a good solution. [1] https://docs.oracle.com/javase/8/docs/technotes/guides/jni/jni-12.html#JNI_OnLoad [2] https://docs.oracle.com/javase/8/docs/technotes/guides/jni/jni-14.html#JNI_OnLoad
03-10-2017

I would not make changes to OnLoad or OnUnload as I think the change belongs in FindClass. For OnUnload the "context" is still unknown/unspecified ie we don't constrain who/how/when it is called (even the mention of 'finalizer' is just an example - and other implementations may continue to do that). For FindClass I suggest adding: jclass FindClass(JNIEnv *env, const char *name); In JDK release 1.1, ... Since JDK 1.2, the Java security model allows ... Since JDK 1.2, when FindClass is called through the Invocation Interface, ... Since JDK 10, when FindClass is called from a library lifecycle function hook the class loader is determined as follows: - for JNI_OnLoad and JNI_OnLoad_L the class loader of the class that is loading the native library is used - for JNI_OnUnload and JNI_OnUnload_L the class loader returned by ClassLoader.getSystemClassLoader is used (as the class loader used at on-load time may no longer exist) The name argument ... --- It's not perfect, but I think sufficient to deal with the current issue. Thanks.
29-09-2017

What do you think of these proposed addition? diff --git a/closed/src/java.se/share/specs/jni/invocation.md b/closed/src/java.se/share/specs/jni/invocation.md --- a/closed/src/java.se/share/specs/jni/invocation.md +++ b/closed/src/java.se/share/specs/jni/invocation.md @@ -187,8 +187,9 @@ `jint JNI_OnLoad(JavaVM *vm, void *reserved);` Optional function defined by dynamically linked libraries. The VM calls -`JNI_OnLoad` when the native library is loaded (for example, through -`System.loadLibrary`). +`JNI_OnLoad` when the native library is loaded and this function uses +the class loader of the class calling `System.loadLibrary` or equivalent API +as the context for finding classes (see [`FindClass`](functions.html#findclass)). In order to make use of functions defined at a certain version of the JNI API, `JNI_OnLoad` must return a constant defining at least that version. For @@ -220,14 +221,15 @@ `void JNI_OnUnload(JavaVM *vm, void *reserved);` -Optional function defined by dynamically linked libraries. The VM calls -`JNI_OnUnload` when the class loader containing the native library is garbage -collected. +Optional function defined by dynamically linked libraries. +When the class loader containing the native library is garbage collected, +the VM registers `JNI_OnUnload` to be invoked when the native library +is being unloaded. It is not specified when the `JNI_OnUnload` function +is invoked and the native library is unloaded. -This function can be used to perform cleanup operations. Because this function -is called in an unknown context (such as from a finalizer), the programmer -should be conservative on using Java VM services, and refrain from arbitrary -Java call-backs. +This function can be used to perform cleanup operations. This function +is called in an unknown context. The programmer should be conservative +on using Java VM services, and refrain from arbitrary Java call-backs.
28-09-2017

Thanks Mandy, I have updated synopsis here and for the CSR. Even the existing two cases in the spec are rather badly specified. The call from a native method needs to account for a native call chain from the native method declared in the class to the native code that actually calls FindClass. The notion of calling "by the invocation interface" is not defined at all and extremely vague. I was trying to think of a better way to express all this, perhaps in terms of finding the first Java frame on the stack, but that's not quite the right formulation - we need to find the first vframe on the stack and that's an implementation detail.
28-09-2017

[~dholmes] this proposal changes the class loader to find classes when FindClass is called from JNI_OnUnload. I don't mind if you change the synposis of this issue to reflect what this issue is about.
27-09-2017

The specification for JNI_FindClass only accounts for two cases: 1. A JNI call from a declared native method - which uses the loader of the class that defines the method 2. A JNI call "through the Invocation Interface" - In which case the system loader is used. There is no definition of what is meant by "through the invocation interface" but I interpret that as being a JNI call from C code, from an attached thread, with no Java frames on the stack. A call from JNI_OnLoad (or OnUnload) does not, to me, fit either of these cases; nor does JNI_OnLoad say anything about the context in which it executes. So it seems we have presumed that this case should mean "use the loader of the class which loaded the native library". A very reasonable approach, but not one defined by the specification as far as I can see. But given this, it is not unreasonable to also use the same interpretation for JNI_OnUnload. So there is a gap in the specification regarding the execution context of the library lifecycle function hooks - other than onUnload being an "unknown context". I do not in itself consider the ability to resurrect the class loader as a bug or a specification issue - it is simply an artifact of using the current classloader from JNI_OnUnload. While loading from the loader being reclaimed is highly dubious, delegating through that loader seems fairly reasonable to me. So in my opinion this is not a bug about the ability to resurrect a class loader but a RFE to address a gap in the JNI specification. Further the spec issue is really with JNI_OnLoad, not JNI_OnUnload, as the latter states that the context is "unknown" - consequently we can change the implementation of JNI_FindClass (which is really the intended goal of this work) without actually needing to make any changes to the JNI spec to permit that. That said I'm not opposed to making concrete statements in JNI_Findclass regarding calls from the library lifecycle hooks.
27-09-2017