yuvals
yuvals

Reputation: 11

Getting a valid JNI environment pointer

I am working on a very large and complex C++ application that is using JNI in several unrelated places for different tasks. The platform is RHEL 6. Because I don't know if a JNI was already created by the application, I am using this method to find a JVM and attach to it:

JOI_RetCode JavaObjectInterface::initJVM()
{
  if (jvm_ == NULL)
  {
    // Is there an existing JVM already created?
    jsize jvm_count = 0;
    jint result = JNI_GetCreatedJavaVMs (&jvm_, 1, &jvm_count);
    if (result != JNI_OK)
      return JOI_ERROR_CHECK_JVM;      

    if (jvm_count == 1)
    {
      // Yes - use it.
      result = jvm_->GetEnv((void**) &jenv_, JNI_VERSION_1_6);
      switch (result)
      {
        case JNI_OK:
          logger::log(CAT_JNI, LL_DEBUG, "Attached to an existing JVM.");
          jenv_ = env;
          break;

        case JNI_EDETACHED:
          result = jvm_->AttachCurrentThread((void**) &jenv_, NULL);   
          if (result != JNI_OK)
            return JOI_ERROR_ATTACH_JVM;

          needToDetach_ = true;
          logger::log(CAT_JNI, LL_DEBUG, "Attached to an existing JVM from another thread.");
          break;

        case JNI_EVERSION:
          logger::log(CAT_JNI, LL_DEBUG, "Attaching to a JVM of the wrong version.");
          return JOI_ERROR_JVM_VERSION;
          break;

        default:
          logger::log(CAT_JNI, LL_DEBUG, "Unknown error Attaching to an existing JVM.");
          return JOI_ERROR_ATTACH_JVM;
          break;
      }
    }
    else
    {
      // No - create a new one.
      result = createJVM();
      if (result != JNI_OK)
        return JOI_ERROR_CREATE_JVM;

      needToDetach_ = false;
      logger::log(CAT_JNI, LL_DEBUG, "Created a new JVM.");
    }
  }

  return JOI_OK;
}

The JVM pointers are defined as class data members as follows:

JavaVM*   jvm_;
JNIEnv*   jenv_;

Now, when a JVM was created elsewhere before I call thid method, the result is that the call to JNI_GetCreatedJavaVMs() returns a JVM pointer, and the call to GetEnv() returns JNI_OK, and updates jenv_ with a pointer that looks valid. So far so good. However, when I next do:

  jclass javaClass = jenv_->FindClass(className); 

I get an exception thrown. I cannot get any data from the exception itself because stderr is redirected to /dev/null, so if I call ExceptionDescribe() the output goes to hell. Interrogating the exception object requires a working JNI to call FindClass("Throwable"), so that's not working either.

The call to FindClass() is good because when I am creating the first JVM, it worksd fine.

Can anyone help me find the stupid mistake that I must have done here somewhere? Thanks, Yuval.

Upvotes: 1

Views: 2856

Answers (1)

technomage
technomage

Reputation: 10069

The JNIEnv pointer is usually thread-specific. It's easiest to simply look it up on demand using the JVM reference.

For whatever block of code that needs to access JNIEnv, look it up in scope for that local block:

JNIEnv* jenv_;
int result = jvm_->GetEnv((void**) &jenv_, JNI_VERSION_1_6);
if (result == JNI_EDETACHED) {
    // attach
}
// Do what you gotta do here
// ...
if (needs_detach) {
    // detach
}

That way you'll be sure to get a valid JNIEnv no matter your context.

The main takeaway from this is "don't cache JNIEnv".

Upvotes: 2

Related Questions