{"record":{"id":"f84ea338d46368e6","repo":"java-native-access/jna","slug":"jna-could-not-detach-thread-on-unload","errorCode":null,"errorMessage":"JNA: could not detach thread on unload\n","messagePattern":"JNA: could not detach thread on unload\n","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"native/dispatch.c","lineNumber":3441,"sourceCode":"    if (*refs[i]) {\n      (*env)->DeleteWeakGlobalRef(env, *refs[i]);\n      *refs[i] = NULL;\n    }\n  }\n\n  JNA_callback_dispose(env);\n\n#ifdef JAWT_HEADLESS_HACK\n  if (jawt_handle != NULL) {\n    FREE_LIBRARY(jawt_handle);\n    jawt_handle = NULL;\n    pJAWT_GetAWT = NULL;\n  }\n#endif\n\n  if (!attached) {\n    if ((*vm)->DetachCurrentThread(vm) != 0) {\n      fprintf(stderr, \"JNA: could not detach thread on unload\\n\");\n    }\n  }\n}\n\nJNIEXPORT void JNICALL\nJava_com_sun_jna_Native_unregister(JNIEnv *env, jclass UNUSED(ncls), jclass cls, jlongArray handles) {\n  jlong* data = (*env)->GetLongArrayElements(env, handles, NULL);\n  int count = (*env)->GetArrayLength(env, handles);\n\n  while (count-- > 0) {\n    method_data* md = (method_data*)L2A(data[count]);\n    if (md->to_native) {\n      unsigned i;\n      for (i=0;i < md->cif.nargs;i++) {\n        if (md->to_native[i])\n          (*env)->DeleteWeakGlobalRef(env, md->to_native[i]);\n      }\n    }","sourceCodeStart":3423,"sourceCodeEnd":3459,"githubUrl":"https://github.com/java-native-access/jna/blob/d036ad9781adad4b66693e8fa7098e4ac665e0a3/native/dispatch.c#L3423-L3459","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before (native host app, worker thread)\nvoid* worker(void* arg) {\n    AttachCurrentThread(jvm, (void**)&env, NULL);\n    run_jna_code(env);\n    DetachCurrentThread(jvm);\n    DetachCurrentThread(jvm);   // double detach -> unload-time detach fails\n    return NULL;\n}\n\n// after\nvoid* worker(void* arg) {\n    AttachCurrentThread(jvm, (void**)&env, NULL);\n    run_jna_code(env);\n    DetachCurrentThread(jvm);   // single, balanced detach before VM shutdown\n    return NULL;\n}","handlingStrategy":"fallback","validationCode":"// Track attach/detach balance per thread before shutdown\nprivate static final Set<Long> attached = ConcurrentHashMap.newKeySet();\n\nstatic void beforeDestroyVM() {\n    if (!attached.isEmpty()) {\n        throw new IllegalStateException(\"Threads still attached and using JNA: \" + attached);\n    }\n}","typeGuard":null,"tryCatchPattern":"// Not catchable: native stderr diagnostic during library unload, no Java exception raised.\n// Defensive pattern is balanced thread lifecycle management:\ntry {\n    useJna(env);\n} finally {\n    if (weAttachedThisThread) {\n        DetachCurrentThread(); // exactly once, before DestroyJavaVM\n    }\n}","preventionTips":["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."],"tags":["jni","native","library-unload","jna","shutdown","threading"],"backgroundTag":"jni-thread-detach-failed","analyzedSha":"d036ad9781adad4b66693e8fa7098e4ac665e0a3","analyzedAt":"2026-09-12T06:50:59.239Z","contentChangedAt":"2026-09-12T06:50:59.239Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}