java-native-access/jna · warning
JNA: Can't attach native thread to VM on unload
Error message
JNA: Can't attach native thread to VM on unload
What it means
This is a diagnostic message printed by JNA's native dispatch code (native/dispatch.c) when the Java VM is being unloaded and the JVM Invocation API call AttachCurrentThread fails on the current native thread. During library unload (e.g. when the JVM calls JNI_OnUnload or the shared library's destructor runs), JNA tries to attach the native thread to the VM so it can obtain a JNIEnv to clean up; if the attach fails it reports this message and aborts cleanup. It means the thread cannot get a valid JNI environment at teardown time, typically because the VM is already partially or fully torn down.
Source
Thrown at native/dispatch.c:3409
&classBoolean, &classPrimitiveBoolean,
&classByte, &classPrimitiveByte,
&classCharacter, &classPrimitiveCharacter,
&classShort, &classPrimitiveShort,
&classInteger, &classPrimitiveInteger,
&classLong, &classPrimitiveLong,
&classFloat, &classPrimitiveFloat,
&classDouble, &classPrimitiveDouble,
&classPointer, &classNative, &classWString,
&classStructure, &classStructureByValue,
&classCallbackReference, &classAttachOptions, &classNativeMapped,
&classIntegerType, &classPointerType,
};
unsigned i;
JNIEnv* env;
int attached = (*vm)->GetEnv(vm, (void*)&env, JNI_VERSION_1_4) == JNI_OK;
if (!attached) {
if ((*vm)->AttachCurrentThread(vm, (void*)&env, NULL) != JNI_OK) {
fprintf(stderr, "JNA: Can't attach native thread to VM on unload\n");
return;
}
}
// Calls back to the Native class are unsafe at this point
//(*env)->CallStaticObjectMethod(env, classNative, MID_Native_dispose);
if (fileEncoding) {
(*env)->DeleteGlobalRef(env, fileEncoding);
fileEncoding = NULL;
}
for (i=0;i < sizeof(refs)/sizeof(refs[0]);i++) {
if (*refs[i]) {
(*env)->DeleteWeakGlobalRef(env, *refs[i]);
*refs[i] = NULL;
}
}View on GitHub (pinned to d036ad9781)
Solutions
- Ensure the JVM outlives the library: call DestroyJavaVM only after all threads that used JNA have finished, and do not dlclose jnidispatch before the VM is destroyed.
- Detach/stop any non-Java threads that made JNA calls before initiating VM shutdown, so they are not mid-use during unload.
- Avoid unloading/reloading shared libraries that link or depend on jnidispatch at runtime; load them once for the process lifetime.
- Upgrade JNA to the latest version; shutdown/unload handling has been hardened across releases.
- Treat this as mostly harmless teardown noise: it is a stderr diagnostic during process exit and usually does not corrupt application state; verify with a graceful shutdown test (no daemon threads using JNA at exit).
Example fix
// before (native host app)
main() {
start_worker_threads_using_jna();
DestroyJavaVM(jvm); // workers may still be attached/in-flight
dlclose(jna_plugin_handle); // unload during/after VM teardown
}
// after
main() {
start_worker_threads_using_jna();
stop_and_join_worker_threads(); // all JNA usage finished first
DestroyJavaVM(jvm);
/* do NOT dlclose the JNA plugin; let process teardown unload it */
} Defensive patterns
Strategy: fallback
Validate before calling
// Java side, before shutdown: ensure no live threads are using JNA
for (Thread t : Thread.getAllStackTraces().keySet()) {
for (StackTraceElement f : t.getStackTrace()) {
if (f.getClassName().startsWith("com.sun.jna.")) {
throw new IllegalStateException("Thread " + t.getName() + " still using JNA at shutdown");
}
}
} Try / catch
// Cannot be caught: it is stderr output from native unload code, not a Java exception.
// Guard instead at the JVM lifecycle level:
try {
system.exitHook(); // trigger graceful shutdown; join all JNA-using threads first
} catch (Throwable ignored) {
// swallow exceptions during shutdown only; never use System.exit() while JNA threads run
} Prevention
- Join all threads that touch JNA before calling System.exit() or DestroyJavaVM.
- Never dlclose/unload libraries depending on jnidispatch while the JVM lives.
- Keep the embedding host's VM lifecycle (attach/detach/destroy) strictly ordered around JNA usage.
- Run graceful-shutdown tests in CI to catch unload-order regressions.
- Pin and regularly update the JNA version; shutdown paths have known historical fixes.
When it happens
Trigger: JVM shutdown or System.exit() triggers unloading of the jnidispatch native library while the calling thread has no prior JNI attachment and AttachCurrentThread returns a non-JNI_OK result; also when the VM is being destroyed (DestroyJavaVM) so no further attachments are possible, or on abnormal process teardown where the VM state no longer accepts attachments.
Common situations: Embedding a JVM inside a native host application and calling DestroyJavaVM while non-Java threads that used JNA are still running; unloading/ reloading a shared library (dlopen/dlclose of jnidispatch or a plugin using JNA) after the VM has begun shutdown; System.exit() from a thread that was never attached; JDK version changes that alter shutdown ordering; signal-triggered abrupt exits (SIGTERM handlers) racing library destructors.
Related errors
- JNA: could not detach thread on unload
- JNA: could not detach thread after callback init
- JNA: extract_value: %s
- JNA: Can't attach native thread to VM for closure handler
- JNA: Out of memory: Can't allocate local frame
AI-assisted analysis of java-native-access/jna@d036ad9781 (2026-09-12).
Data as JSON: /api/errors/80ab0d6ea0f4c6d9.
Report an issue: GitHub.