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

  1. 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.
  2. Detach/stop any non-Java threads that made JNA calls before initiating VM shutdown, so they are not mid-use during unload.
  3. Avoid unloading/reloading shared libraries that link or depend on jnidispatch at runtime; load them once for the process lifetime.
  4. Upgrade JNA to the latest version; shutdown/unload handling has been hardened across releases.
  5. 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

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


AI-assisted analysis of java-native-access/jna@d036ad9781 (2026-09-12). Data as JSON: /api/errors/80ab0d6ea0f4c6d9. Report an issue: GitHub.