The three JNI direct buffer API entry points are not declared as JNI_ENTRY and so do not perform thread-state-transitions on entry and will not detect a pending safepoint, meaning they can commence execution in a daemon thread even if the VM has already been terminated or is in the process of terminating. These API entry points all have the same initial form:
extern "C" jobject JNICALL jni_NewDirectByteBuffer(JNIEnv *env, void* address, jlong capacity)
{
// thread_from_jni_environment() will block if VM is gone.
JavaThread* thread = JavaThread::thread_from_jni_environment(env);
HOTSPOT_JNI_NEWDIRECTBYTEBUFFER_ENTRY(env, address, capacity);
if (!directBufferSupportInitializeEnded) {
if (!initializeDirectBufferSupport(env, thread)) {
HOTSPOT_JNI_NEWDIRECTBYTEBUFFER_RETURN(NULL);
return NULL;
}
}
The intent/expectation is that if the VM is, or has, terminated then the call to thread_from_jni_environment will block forever. The problem is that termination can happen after that point. If the code reaches the main logic, which involves invoking other JNI methods then those JNI_ENTRY points will also cause the thread to block. But if this by chance is the first use of the JNI direct buffer API then the daemon thread will attempt to initialize the direct bytebuffer support, and that in turn will execute the following code:
initializeDirectBufferSupport -> lookupDirectBufferClasses -> lookupOne
TempNewSymbol sym = SymbolTable::new_symbol(name);
jclass result = find_class_from_class_loader(env, sym, true, loader, protection_domain, true, CHECK_NULL);
and this code could easily trigger a crash if the VM has in fact already exited (we are assuming an embedded VM in a host process).
extern "C" jobject JNICALL jni_NewDirectByteBuffer(JNIEnv *env, void* address, jlong capacity)
{
// thread_from_jni_environment() will block if VM is gone.
JavaThread* thread = JavaThread::thread_from_jni_environment(env);
HOTSPOT_JNI_NEWDIRECTBYTEBUFFER_ENTRY(env, address, capacity);
if (!directBufferSupportInitializeEnded) {
if (!initializeDirectBufferSupport(env, thread)) {
HOTSPOT_JNI_NEWDIRECTBYTEBUFFER_RETURN(NULL);
return NULL;
}
}
The intent/expectation is that if the VM is, or has, terminated then the call to thread_from_jni_environment will block forever. The problem is that termination can happen after that point. If the code reaches the main logic, which involves invoking other JNI methods then those JNI_ENTRY points will also cause the thread to block. But if this by chance is the first use of the JNI direct buffer API then the daemon thread will attempt to initialize the direct bytebuffer support, and that in turn will execute the following code:
initializeDirectBufferSupport -> lookupDirectBufferClasses -> lookupOne
TempNewSymbol sym = SymbolTable::new_symbol(name);
jclass result = find_class_from_class_loader(env, sym, true, loader, protection_domain, true, CHECK_NULL);
and this code could easily trigger a crash if the VM has in fact already exited (we are assuming an embedded VM in a host process).