{"record":{"id":"80ab0d6ea0f4c6d9","repo":"java-native-access/jna","slug":"jna-can-t-attach-native-thread-to-vm-on-unload","errorCode":null,"errorMessage":"JNA: Can't attach native thread to VM on unload\n","messagePattern":"JNA: Can't attach native thread to VM on unload\n","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"native/dispatch.c","lineNumber":3409,"sourceCode":"    &classBoolean, &classPrimitiveBoolean,\n    &classByte, &classPrimitiveByte,\n    &classCharacter, &classPrimitiveCharacter,\n    &classShort, &classPrimitiveShort,\n    &classInteger, &classPrimitiveInteger,\n    &classLong, &classPrimitiveLong,\n    &classFloat, &classPrimitiveFloat,\n    &classDouble, &classPrimitiveDouble,\n    &classPointer, &classNative, &classWString,\n    &classStructure, &classStructureByValue,\n    &classCallbackReference, &classAttachOptions, &classNativeMapped,\n    &classIntegerType, &classPointerType,\n  };\n  unsigned i;\n  JNIEnv* env;\n  int attached = (*vm)->GetEnv(vm, (void*)&env, JNI_VERSION_1_4) == JNI_OK;\n  if (!attached) {\n    if ((*vm)->AttachCurrentThread(vm, (void*)&env, NULL) != JNI_OK) {\n      fprintf(stderr, \"JNA: Can't attach native thread to VM on unload\\n\");\n      return;\n    }\n  }\n\n  // Calls back to the Native class are unsafe at this point\n  //(*env)->CallStaticObjectMethod(env, classNative, MID_Native_dispose);\n\n  if (fileEncoding) {\n    (*env)->DeleteGlobalRef(env, fileEncoding);\n    fileEncoding = NULL;\n  }\n\n  for (i=0;i < sizeof(refs)/sizeof(refs[0]);i++) {\n    if (*refs[i]) {\n      (*env)->DeleteWeakGlobalRef(env, *refs[i]);\n      *refs[i] = NULL;\n    }\n  }","sourceCodeStart":3391,"sourceCodeEnd":3427,"githubUrl":"https://github.com/java-native-access/jna/blob/d036ad9781adad4b66693e8fa7098e4ac665e0a3/native/dispatch.c#L3391-L3427","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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)."],"exampleFix":"// before (native host app)\nmain() {\n    start_worker_threads_using_jna();\n    DestroyJavaVM(jvm);           // workers may still be attached/in-flight\n    dlclose(jna_plugin_handle);   // unload during/after VM teardown\n}\n\n// after\nmain() {\n    start_worker_threads_using_jna();\n    stop_and_join_worker_threads();   // all JNA usage finished first\n    DestroyJavaVM(jvm);\n    /* do NOT dlclose the JNA plugin; let process teardown unload it */\n}","handlingStrategy":"fallback","validationCode":"// Java side, before shutdown: ensure no live threads are using JNA\nfor (Thread t : Thread.getAllStackTraces().keySet()) {\n    for (StackTraceElement f : t.getStackTrace()) {\n        if (f.getClassName().startsWith(\"com.sun.jna.\")) {\n            throw new IllegalStateException(\"Thread \" + t.getName() + \" still using JNA at shutdown\");\n        }\n    }\n}","typeGuard":null,"tryCatchPattern":"// Cannot be caught: it is stderr output from native unload code, not a Java exception.\n// Guard instead at the JVM lifecycle level:\ntry {\n    system.exitHook(); // trigger graceful shutdown; join all JNA-using threads first\n} catch (Throwable ignored) {\n    // swallow exceptions during shutdown only; never use System.exit() while JNA threads run\n}","preventionTips":["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."],"tags":["jni","native","library-unload","jna","shutdown"],"backgroundTag":"jni-thread-attach-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"}