Java Native Interface
| Company type | Software interface |
|---|---|
| Industry | Software development |
| Products | Foreign 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">
- include <stdio.h>
- 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:
Linkerto call foreign functions,SymbolLookupfor resolving symbols within libraries,FunctionDescriptorto 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
- Oracle's JNI 6.0 API Documentation
- Java Native Interface: Programmer’s Guide and Specification
- Apple’s XCode JNI Documentation
- Handling Exceptions in JNI
- Java Link (a contemporary C++17 wrapper for JNI)