java-native-access/jna · warning
JNA: could not detach thread on unload
Error message
JNA: could not detach thread on unload
What it means
This is a diagnostic message from JNA's native dispatch code printed when, during native library unload/VM shutdown, the code tries to DetachCurrentThread on a thread it did not attach and the call returns non-zero (failure). The JVM Invocation API rejects detaching a thread that is not currently attached (or is already detached), so JNA reports that it could not clean up the thread's VM association. Like the attach counterpart, it is stderr teardown noise rather than a thrown exception, but it signals mismatched attach/detach bookkeeping at shutdown.
Source
Thrown at native/dispatch.c:3441
if (*refs[i]) {
(*env)->DeleteWeakGlobalRef(env, *refs[i]);
*refs[i] = NULL;
}
}
JNA_callback_dispose(env);
#ifdef JAWT_HEADLESS_HACK
if (jawt_handle != NULL) {
FREE_LIBRARY(jawt_handle);
jawt_handle = NULL;
pJAWT_GetAWT = NULL;
}
#endif
if (!attached) {
if ((*vm)->DetachCurrentThread(vm) != 0) {
fprintf(stderr, "JNA: could not detach thread on unload\n");
}
}
}
JNIEXPORT void JNICALL
Java_com_sun_jna_Native_unregister(JNIEnv *env, jclass UNUSED(ncls), jclass cls, jlongArray handles) {
jlong* data = (*env)->GetLongArrayElements(env, handles, NULL);
int count = (*env)->GetArrayLength(env, handles);
while (count-- > 0) {
method_data* md = (method_data*)L2A(data[count]);
if (md->to_native) {
unsigned i;
for (i=0;i < md->cif.nargs;i++) {
if (md->to_native[i])
(*env)->DeleteWeakGlobalRef(env, md->to_native[i]);
}
}View on GitHub (pinned to d036ad9781)
Solutions
- Ensure each thread attaches and detaches exactly once: if you call AttachCurrentThread yourself, call DetachCurrentThread yourself before JVM shutdown, and never detach the same thread twice.
- Stop and join all threads using JNA before calling DestroyJavaVM so the VM teardown does not race JNA's unload-time detach.
- Do not detach threads that the JVM attached itself (e.g. main thread); let the JVM manage those.
- Upgrade JNA to the latest version for improved shutdown handling.
- If seen only at process exit with no functional impact, it can be safely ignored as benign teardown noise; confirm by checking the application completes shutdown cleanly.
Example fix
// before (native host app, worker thread)
void* worker(void* arg) {
AttachCurrentThread(jvm, (void**)&env, NULL);
run_jna_code(env);
DetachCurrentThread(jvm);
DetachCurrentThread(jvm); // double detach -> unload-time detach fails
return NULL;
}
// after
void* worker(void* arg) {
AttachCurrentThread(jvm, (void**)&env, NULL);
run_jna_code(env);
DetachCurrentThread(jvm); // single, balanced detach before VM shutdown
return NULL;
} Defensive patterns
Strategy: fallback
Validate before calling
// Track attach/detach balance per thread before shutdown
private static final Set<Long> attached = ConcurrentHashMap.newKeySet();
static void beforeDestroyVM() {
if (!attached.isEmpty()) {
throw new IllegalStateException("Threads still attached and using JNA: " + attached);
}
} Try / catch
// Not catchable: native stderr diagnostic during library unload, no Java exception raised.
// Defensive pattern is balanced thread lifecycle management:
try {
useJna(env);
} finally {
if (weAttachedThisThread) {
DetachCurrentThread(); // exactly once, before DestroyJavaVM
}
} Prevention
- Attach and detach each native thread exactly once; balance every AttachCurrentThread with one DetachCurrentThread.
- Never call DetachCurrentThread on JVM-attached threads (e.g. the main thread).
- Stop and join all JNA-using threads before DestroyJavaVM so VM teardown cannot race the detach.
- Avoid unloading/reloading JNA-dependent native libraries at runtime.
- If the message only appears at process exit, treat it as benign and verify shutdown completes cleanly.
When it happens
Trigger: VM shutdown/unload path in dispatch.c reaches the DetachCurrentThread call (because the `attached` flag indicated this code path attached the thread earlier, or the guard block executes) and DetachCurrentThread returns non-zero — e.g. the thread was already detached, the VM is in the process of being destroyed and has disowned threads, or the thread was attached with a different mechanism and concurrently detached elsewhere.
Common situations: DestroyJavaVM racing worker threads that called JNA (VM detaches threads itself first, then JNA's unload handler also tries); a thread already detached by user code via DetachCurrentThread before library unload; nested/double unload of the native library; embedding hosts (e.g. custom launchers, app servers) that manage thread attach/detach lifecycles themselves; signal-based abrupt shutdowns where ordering is undefined.
Related errors
- JNA: Can't attach native thread to VM on unload
- JNA: could not detach native thread (automatic)
- JNA: could not detach thread after callback init
- JNA: extract_value: %s
- JNA: Can't attach native thread to VM for closure handler
AI-assisted analysis of java-native-access/jna@d036ad9781 (2026-09-12).
Data as JSON: /api/errors/f84ea338d46368e6.
Report an issue: GitHub.