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

  1. 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.
  2. Stop and join all threads using JNA before calling DestroyJavaVM so the VM teardown does not race JNA's unload-time detach.
  3. Do not detach threads that the JVM attached itself (e.g. main thread); let the JVM manage those.
  4. Upgrade JNA to the latest version for improved shutdown handling.
  5. 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

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


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