Jump to content

Java Native Interface

From Alfawiki
Java Native Interface
Company typeSoftware interface
IndustrySoftware development
ProductsForeign function interface for Java

The Java Native Interface (JNI) constitutes a foreign function interface mechanism that permits Java programs executing within a Java virtual machine (JVM) to invoke and be invoked by native applications and libraries developed in languages such as C, C++, or assembly.

With the release of Java 22, the Foreign Function and Memory API has been introduced, designed to replace JNI as the preferred interface.

Objectives

JNI facilitates the creation of native methods when certain application components cannot be fully realized in Java—for instance, when platform-specific capabilities or external libraries are not supported by the standard Java class library. It also enables seamless integration between Java and pre-existing non-Java software. Numerous core Java library classes depend on JNI to provide essential services such as file input/output and audio processing, delivering efficient, platform-aware APIs while maintaining platform independence and safety.

Through JNI, native methods gain the ability to interact with Java objects much like Java code does. These native routines can instantiate, examine, and manipulate Java objects, as well as access those generated by Java applications.

Access to JNI calls is restricted to applications and signed applets.

Employing JNI typically compromises Java’s inherent platform portability; a common approach involves implementing distinct JNI versions tailored for each target platform, loading the appropriate one dynamically during runtime.

In addition to connecting with Java code, native modules can interact with a Java graphical user interface via the Java AWT Native Interface, which has been available since J2SE 1.3.

JNI also permits direct invocation of assembly routines without requiring an intermediate C layer. Conversely, assembly code can directly call into Java programs.

Design

Within the JNI architecture, native functions are defined in separate source files with extensions such as .c or .cpp; using C++ provides a somewhat more straightforward JNI interface. When the JVM calls a native function, it supplies a JNIEnv pointer, a jobject reference, along with any additional parameters specified by the corresponding Java method. For instance, converting a Java string into a native C string might look like this:

<syntaxhighlight lang="cpp"> extern "C" JNIEXPORT void JNICALL Java_ClassName_MethodName

 (JNIEnv *env, jobject obj, jstring javaString)

{

   const char *nativeString = env->GetStringUTFChars(javaString, 0);
   // Process nativeString here
   env->ReleaseStringUTFChars(javaString, nativeString);

} </syntaxhighlight>

The env pointer serves as the gateway to JVM operations, allowing manipulation of Java objects and performing tasks such as converting arrays and strings, creating new objects, or handling exceptions. The obj parameter refers to the specific Java object that declared the native method.

Primitive data types have corresponding JNI types for conversion between native and Java representations; however, complex data structures like objects, arrays, and strings require explicit transformation using methods provided through JNIEnv.

Every native function receives a JNI environment pointer (JNIEnv*) to communicate with the JVM. This pointer is valid only within the thread invoking it; other threads must attach themselves by calling AttachCurrentThread() before accessing JNI features and detach afterward via DetachCurrentThread().

Memory allocated outside of the JVM by native code is not managed by JNI’s garbage collector; therefore, manual memory management is necessary to avoid leaks.

On Linux and Solaris platforms, native code acting as signal handlers can intercept signals destined for the JVM; employing a chain of responsibility design pattern enhances compatibility. On Windows systems, Structured Exception Handling (SEH) encapsulates native code execution to catch hardware-level exceptions—such as null pointer dereferences—preventing these exceptions from propagating unchecked into Java.

Functions including NewStringUTF and GetStringUTFChars utilize "modified UTF-8," which differs from standard UTF-8 encoding particularly regarding null characters and code points beyond the Basic Multilingual Plane. Misinterpretation often occurs when these are treated as conventional UTF-8. To ensure accurate encoding conversions, it is advisable to use UTF-16-based functions (e.g., NewString, GetStringChars) combined with explicit UTF-16 to UTF-8 conversion routines.

A typical example of JNI usage begins with including <jni.h>:

<syntaxhighlight lang="c">

  1. include <stdio.h>
  2. include <jni.h>

JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *env, jobject obj) {

   printf("Hello from C!\n");

} </syntaxhighlight>

This native method can be invoked from Java as follows:

<syntaxhighlight lang="java"> public class HelloJNI {

   static {
       System.loadLibrary("hello");
   }
   public native void sayHello();
   public static void main(String[] args) {
       new HelloJNI().sayHello();
   }

} </syntaxhighlight>

Mapping types

The following table presents the correspondence between Java (JNI) types and their native counterparts.

C Type Java Language Type Description Type signature
unsigned char
uint8_t
jboolean 8-bit unsigned integer Z
signed char
int8_t
jbyte 8-bit signed integer B
unsigned short
uint16_t
jchar 16-bit unsigned integer C
short
int16_t
jshort 16-bit signed integer S
int
int32_t
jint 32-bit signed integer I
long long
int64_t
jlong 64-bit signed integer J
float jfloat 32-bit floating point F
double jdouble 64-bit floating point D
void V

Class signatures follow the pattern "Lfully-qualified-class;", for example, "Ljava/lang/String;" denotes the Java String class. Arrays are indicated by prefixing with [, such as [I representing an array of integers (int[]). These types can be used interchangeably; for instance, jint corresponds directly to int without requiring explicit casting. Nonetheless, extra care is necessary when mapping Java Strings or arrays to native equivalents to prevent JVM instability.

Performance

JNI introduces significant performance overhead due to several factors:

  • Invoking JNI functions incurs high cost, particularly when done repeatedly.
  • Native methods are ineligible for JVM inlining or just-in-time compilation.
  • Java arrays often undergo copying between JVM and native memory, leading to costs proportional to array length.
  • Accessing Java objects from native code involves reflection-style lookups using string signatures, which are inherently slow and prone to errors.
  • Managing Java Strings necessitates copying because of encoding conversions and length handling, further impacting efficiency.

Foreign Function and Memory API

Java 22 introduced the Foreign Function and Memory API within the java.lang.foreign package as a modern alternative to JNI, reducing boilerplate code and eliminating the need for <jni.h>. This API utilizes region-based memory management through the MemorySegment class, which models contiguous memory regions either inside or outside the JVM heap. Memory allocation is managed by an Arena, responsible for controlling the lifespan of allocated segments.

This API allows direct native memory allocation via methods such as Arena.allocate() and MemorySegment.allocateNative(), with deallocation performed by invoking Arena.close() or MemorySegment.close().

As a foreign function interface, it offers:

  • Linker to call foreign functions,
  • SymbolLookup for resolving symbols within libraries,
  • FunctionDescriptor to specify foreign function signatures.

Practical examples demonstrate allocating memory segments and calling native functions like printf and strlen, showcasing efficient interoperability without the complexity associated with JNI.

Alternatives

Microsoft’s proprietary Java Virtual Machine (Visual J++) incorporated a native interface called the Raw Native Interface (RNI) alongside J/Direct, which facilitated invoking existing native APIs such as the Windows API. However, following the Sun–Microsoft litigation, Visual J++ is no longer supported.

Compared to JNI, RNI provided a simpler approach by enabling direct access to Java objects without requiring management of a Java environment pointer; tools were available to generate header files from Java classes automatically. Similarly, J/Direct eased native calls relative to JNI’s reliance on intermediary libraries.

Java Native Access (JNA) is a community-maintained library that permits Java applications to interface with native shared libraries without using JNI, though it necessitates distributing its jar file. The decision involves balancing JNI’s complexity against JNA’s comparatively slower execution. Despite alternatives, JNI remains fundamental within core Java.

Since Java 22, the Foreign Function and Memory API is favored over JNI due to its simplified design and reduced boilerplate requirements.

See also

Bibliography


External links