JavaData & JavaHow to Create a JVM Instance in JNI

How to Create a JVM Instance in JNI

Developer.com content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

JNI paves the way to use native code in Java applications. It provides a provision for low-level system assimilation even though it runs within the secure boundary of a virtual machine. Programming languages such as C/C++ are perfectly suitable for stooping down to very low-level code. JNI virtually is the bridge that makes two otherwise unlikely languages work in unison. JNI’s motive is to make seamless utilization of native code extension in a Java program. That’s one part of the story. What if a C/C++ program wants to utilize Java code? The Java API is extensive and undoubtedly contains rich features. It can be put to some good use in leveraging the productivity of C/C++ if used appropriately. Fortunately, JNI has provision for that, too. The whole idea of this article is to shed some light on the way to utilizing Java code in C++.

Why Do We Need JVM Instances?

In a nutshell, creating a JVM instance is the flip side of JNI where a native application uses Java code. One obvious reason for such a requirement is to use Java features that the native application does not support. But, to put it in a more concrete way:

  • Suppose there is an legacy application already coded in Java and the new native application wants to use it as an utility rather than rewriting the functionality from scratch.
  • Rarely any language can match the rich set of class libraries in Java, not even C/C++. It’s an advantage if a native application written in C/C++ enriches itself with the already established class libraries of Java.

These two reasons perhaps open up the horizon of ideas about the utility of reverse JNI and why JNI developers kept both options open. JNI is the door through which this reflexive relation between Java and native code interacts.

JVM Instantiation Process

The native application creates and loads a JVM instance and embeds it within the native application. Java classes that are supposed to be used by the native application are loaded and run in the embedded JVM. The part that JNI plays here is that it supplies the API to create and load JVM in native code. This specific part of the JNI API is called Invocation API, because it yields in invoking the JVM.

Creating a JVM instance in native code is simple. All we need to do is set up a few initial arguments and call an Invocation API function named JNI_CreateJavaVM() to create the JVM. The initial arguments are prepared to be passed to the JVM eventually. JNI provides a structure called JavaVMInitArgs to pass initial arguments to JVM. The definition of the structure is as follows

typedef struct JavaVMInitArgs {
   jint version;
   jint nOptions;
   JavaVMOption *options;
   jboolean ignoreUnrecognized;
}
JavaVMInitArgs;
  • The version field indicates the JNI version and must be set to at least JNI_VERSION_1_2.
  • The nOptions field indicates the number of options passed to JVM.
  • The option is a pointer to an array of JavaVMOption structure. The definition of the structure is as follows:
typedef struct JavaVMOption {
   char *optionString;
   void *extraInfo;
} JavaVMOption;
  • The optionString field indicates the default platform encoding for the JVM.
  • The extraInfo field represents function hook for redirecting a JVM message, a JVM abort hook, or a JVM exit hook. The type of hook is determined by the value passed to the optionString.

The structure options are generally initialized with value as follows:

JavaVMOption jvmopt[3];

jvmopt[0].optionString = "printf";
jvmopt[0].extraInfo = jvmMsgRedirection_hook;

jvmopt[1].optionString = "abort";
jvmopt[1].extraInfo = jvmAbort_hook;

jvmopt[2].optionString = "exit";
jvmopt[2].extraInfo = jvmExit_hook;
  • The ignoreUnrecognized is a boolean field, set to either JNI_TRUE or JNI_FALSE. If the field is set to true, the value of the JNI_CreateJavaVM() function will ignore any unrecognized value passed to the option field; otherwise, it would return JNI_ERR as soon as it encounters any unrecognized value.

Once the initialization process is completed, a JVM instance can be created as follows:

JNIEnv *jniEnv;
JavaVM *javaVM;
long flag;
flag = JNI_CreateJavaVM(&javaVM, (void**)&jniEnv,
   &vm_args);

if (flag == JNI_ERR) {
   printf("Error creating VM. Exiting...n");
   return 1;
}

Once the JVM instance is created, the entire JVM is at the disposal of the native application. The JNI API makes sure that the native application can find a class, create an object, and execute the method of that object.

Once tinkering is complete or the purpose is served, we must destroy the JVM instance as follows:

javaVM->DestroyJavaVM();

Putting It All Together

Create a Java class, say Demo.java. The static method in the class would take a string parameter and print the value.

package org.jnijvm;
import java.util.Random;
public class Demo{
   public static void greet(String name) {
      System.out.print("Hi! "+name);
   }
}

Now, create a C++ application, say jvmdemo.cpp, that uses the greet function of Java in native code.

#include <jni.h>
#include <iostream>
#include <string>
using namespace std;

int main(int argc, char **argv) {

   JavaVMOption jvmopt[1];
   jvmopt[0].optionString = "-Djava.class.path=" + ".";

   JavaVMInitArgs vmArgs;
   vmArgs.version = JNI_VERSION_1_2;
   vmArgs.nOptions = 1;
   vmArgs.options = jvmopt;
   vmArgs.ignoreUnrecognized = JNI_TRUE;

   // Create the JVM
   JavaVM *javaVM;
   JNIEnv *jniEnv;
   long flag = JNI_CreateJavaVM(&javaVM, (void**)
      &jniEnv, &vmArgs);
   if (flag == JNI_ERR) {
      cout << "Error creating VM. Exiting...n";
      return 1;
   }

   jclass jcls = env->FindClass("org/jnijvm/Demo");
   if (jcls == NULL) {
      jniEnv->ExceptionDescribe();
      javaVM->DestroyJavaVM();
      return 1;
   }
   if (jcls != NULL) {
      jmethodID methodId = env->GetStaticMethodID(jcls,
         "greet", "(Ljava/lang/String;)V");
      if (methodId != NULL) {
         jstring str = env->NewStringUTF(10);
         env->CallStaticVoidMethod(jcls, methodId, str);
         if (env->ExceptionCheck()) {
            env->ExceptionDescribe();
            env->ExceptionClear();
         }
      }
   }

   javaVM->DestroyJavaVM();
   return 0;
}

Compiling Native C++ Code

In the case of the Windows platform, compile with the following command from the command line:

C:/> g++ -IC:/java/include -IC:/java/include/win32
   -o jvmdemo jvmdemo.cpp C:/java/lib/jvm.lib

On a Linux platform, use a command similar to:

$ g++ -fPIC -I/home/mano/java/include
   -I/home/mano/java/include/linux
   -o jvmdemo jvmdemo.cpp /home/mano/java/
   jre/lib/amd64/server/libjvm.so

Conclusion

For beginners, one thing is certain: JNI programming is not a walk in the park, especially due to the cumbersome steps involved in setting up the code and compiling. If only you are lucky, your first application will not have any glitches, exceptions, and so forth. Somes hint to the areas which often creates problem are: Make sure that the JAVA_HOME path, CLASSPATH is rightly configured and the g++ compiler is installed properly and configured correctly so that while compiling, it finds the jni.h header files, and so forth. Last but not the least: JNI is a powerful feature that is better off not used if a workaround is possible, especially in a commercial application. It it, however, not a providence. We can always find out ourselves what the hazards are, at least in the cozy corner of own R&D lab. 😉

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories