Java Native Interface Specification - Chapter 2 : Design Overview

JNI官方文档:https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/jniTOC.html

Chapter   2 

Design Overview

JNI Interface Functions and Pointers

Native code accesses Java VM features by calling JNI functions. JNI functions are available through an interface pointer. An interface pointer is a pointer to a pointer. This pointer points to an array of pointers, each of which points to an interface function. Every interface function is at a predefined offset inside the array. Figure 2-1 illustrates the organization of an interface pointer.

The JNI interface is organized like a C++ virtual function table or a COM interface. The advantage to using an interface table, rather than hard-wired function entries, is that the JNI name space becomes separate from the native code. A VM can easily provide multiple versions of JNI function tables. For example, the VM may support two JNI function tables:

JNI接口层被设计成为指向指针的指针,该结构与虚函数表类似。该设计的优点在于:JNI的name space与原生代码剥离。

  • one performs thorough illegal argument checks, and is suitable for debugging;
  • the other performs the minimal amount of checking required by the JNI specification, and is therefore more efficient.

Jvm提供2份JNI函数表,一份对参数进行合法性检查,适用于调试;另一份仅保证最小程度的参数检测,从而更有效率

The JNI interface pointer is only valid in the current thread. A native method, therefore, must not pass the interface pointer from one thread to another. A VM implementing the JNI may allocate and store thread-local data in the area pointed to by the JNI interface pointer.

JNI interface pointer 依附于当前线程,因而,原生方法中,必不可能在另外一个线程内调用Jni interface pointer。JNI interface pointer使用thread-local的特性。

Native methods receive the JNI interface pointer as an argument. The VM is guaranteed to pass the same interface pointer to a native method when it makes multiple calls to the native method from the same Java thread. However, a native method can be called from different Java threads, and therefore may receive different JNI interface pointers.

JNI interface pointer被作为参数传递给native mothod。当单个java线程中,存在多个对native method的调用时,JVM保证,传递给native method的JNI interface pointer一致。然而,如果,不同的java线程,调用同一个native method,则native method会收到不同的JNI interface pointer。

Compiling, Loading and Linking Native Methods

Since the Java VM is multithreaded, native libraries should also be compiled and linked with multithread aware native compilers. For example, the -mt flag should be used for C++ code compiled with the Sun Studio compiler. For code complied with the GNU gcc compiler, the flags -D_REENTRANT or -D_POSIX_C_SOURCE should be used. For more information please refer to the native compiler documentation.

JVM多线程,因此,原生lib需要被编译成支持多线程特性。

Native methods are loaded with the System.loadLibrary method. In the following example, the class initialization method loads a platform-specific native library in which the native method f is defined:

原生方法由System.loadLibrary加载使用。

 1 package pkg;  
 2 
 3 class Cls { 
 4 
 5      native double f(int i, String s); 
 6 
 7      static { 
 8 
 9          System.loadLibrary(“pkg_Cls”); 
10 
11      } 
12 
13 } 

 

The argument to System.loadLibrary is a library name chosen arbitrarily by the programmer. The system follows a standard, but platform-specific, approach to convert the library name to a native library name. For example, a Solaris system converts the name pkg_Cls to libpkg_Cls.so, while a Win32 system converts the same pkg_Cls name to pkg_Cls.dll.

System.loadLibrary有以下特点:

  • lib的后缀由平台指定,如linux系统上,lib的后缀为so,在win32系统上,lib的后缀为dll

The programmer may use a single library to store all the native methods needed by any number of classes, as long as these classes are to be loaded with the same class loader. The VM internally maintains a list of loaded native libraries for each class loader. Vendors should choose native library names that minimize the chance of name clashes.

If the underlying operating system does not support dynamic linking, all native methods must be prelinked with the VM. In this case, the VM completes the System.loadLibrary call without actually loading the library.

如果底层os不支持动态链接,需要把所有的native method预链接到JVM上。这种情况下,JVM实际上没有加载lib,却依然能够完成loadLibrary操作。

The programmer can also call the JNI function RegisterNatives() to register the native methods associated with a class. The RegisterNatives() function is particularly useful with statically linked functions.

开发人员也可以使用RegisterNatives注册native method。当静态链接函数时,RegisterNatives函数很有效。

Resolving Native Method Names

Dynamic linkers resolve entries based on their names. A native method name is concatenated from the following components:

naitive method方法的名称,由以下部分组成:

  • the prefix Java_      前缀
  • a mangled fully-qualified class name       完整的类名,含包前缀
  • an underscore (“_”) separator       下划线
  • a mangled method name       method方法名
  • for overloaded native methods, two underscores (“__”) followed by the mangled argument signature          对于重载的方法名,2个下划线后紧跟参数签名
1 class Cls1 { 
2 
3   int g(int i); 
4 
5   native int g(double d); 
6 
7 } 

上述的native method g并不属于重载,因为另外一个g不是native方法

Native Method Arguments

对于java声明的native方法,如下所示:

1 package pkg;  
2 
3 class Cls { 
4 
5      native double f(int i, String s); 
6 
7      ... 
8 
9 } 

 

对应c风格的

 1 jdouble Java_pkg_Cls_f__ILjava_lang_String_2 (
 2      JNIEnv *env,        /* interface pointer */
 3      jobject obj,        /* "this" pointer */
 4      jint i,             /* argument #1 */
 5      jstring s)          /* argument #2 */
 6 {
 7      /* Obtain a C-copy of the Java string */
 8      const char *str = (*env)->GetStringUTFChars(env, s, 0);
 9 
10      /* process the string */
11      ...
12 
13      /* Now we are done with str */
14      (*env)->ReleaseStringUTFChars(env, s, str);
15 
16      return ...
17 }

 

对应c++风格的

 1 extern "C" /* specify the C calling convention */  
 2 
 3 jdouble Java_pkg_Cls_f__ILjava_lang_String_2 ( 
 4 
 5      JNIEnv *env,        /* interface pointer */ 
 6 
 7      jobject obj,        /* "this" pointer */ 
 8 
 9      jint i,             /* argument #1 */ 
10 
11      jstring s)          /* argument #2 */ 
12 
13 { 
14 
15      const char *str = env->GetStringUTFChars(s, 0); 
16 
17      ... 
18 
19      env->ReleaseStringUTFChars(s, str); 
20 
21      return ... 
22 
23 } 

 

With C++, the extra level of indirection and the interface pointer argument disappear from the source code. However, the underlying mechanism is exactly the same as with C. In C++, JNI functions are defined as inline member functions that expand to their C counterparts.

Referencing Java Objects

Primitive types, such as integers, characters, and so on, are copied between Java and native code. Arbitrary Java objects, on the other hand, are passed by reference. The VM must keep track of all objects that have been passed to the native code, so that these objects are not freed by the garbage collector. The native code, in turn, must have a way to inform the VM that it no longer needs the objects. In addition, the garbage collector must be able to move an object referred to by the native code.

初始数据类型(如int、char、float等),在Java和native code直接传递副本。而任意的Java对象(如java string),则传递引用。

JVM必须追踪所有传递到native的引用类型,因此,这些被引用的对象不能被垃圾收集器释放。

native code不再使用对象引用时,必须通知JVM,以便垃圾收集器释放掉这些对象。

Global and Local References

The JNI divides object references used by the native code into two categories: local and global references. Local references are valid for the duration of a native method call, and are automatically freed after the native method returns. Global references remain valid until they are explicitly freed.

JNI有两类对象引用:局部和全局引用。

局部对象引用仅存活在method方法期间,return后释放referfence。全局对象只在显示释放后才会销毁

Objects are passed to native methods as local references. All Java objects returned by JNI functions are local references. The JNI allows the programmer to create global references from local references. JNI functions that expect Java objects accept both global and local references. A native method may return a local or global reference to the VM as its result.

局部引用:

  • 通过JNI method传递的对象引用是局部引用
  • 通过JNI 调用返回的Java对象也是局部引用

JNI允许从布局引用创建全局引用。JNI函数希望所有的Java对象都能接收局部和全局引用。native method可以返回局部对象,也能返回全局对象。

In most cases, the programmer should rely on the VM to free all local references after the native method returns. However, there are times when the programmer should explicitly free a local reference. Consider, for example, the following situations:

通常情况下,当native method返回时,程序员应该通知JVM释放掉局部引用。然后,也有一些需要程序员自己释放的情况:

  • A native method accesses a large Java object, thereby creating a local reference to the Java object. The native method then performs additional computation before returning to the caller. The local reference to the large Java object will prevent the object from being garbage collected, even if the object is no longer used in the remainder of the computation.
  • native method接收到一个很大的Java对象引用。native method获取的局部引用会组织java对象被垃圾收集器回收,即使在native method返回前该大对象不再使用。所以应该在native 返回前通知JVM提前释放
  • A native method creates a large number of local references, although not all of them are used at the same time. Since the VM needs a certain amount of space to keep track of a local reference, creating too many local references may cause the system to run out of memory. For example, a native method loops through a large array of objects, retrieves the elements as local references, and operates on one element at each iteration. After each iteration, the programmer no longer needs the local reference to the array element.
  • native method接收到大量的局部引用,即便有些局部引用现在并不需要。大量的局部引用可能会导致系统内存溢出。所以,应该把不需要的局部引用提前释放

 The JNI allows the programmer to manually delete local references at any point within a native method. To ensure that programmers can manually free local references, JNI functions are not allowed to create extra local references, except for references they return as the result.

JNI运行程序员在native method运行期间手动释放调用局部引用,且JNI函数不允许创建额外的局部引用,除了返回时创建的java object 引用。

Local references are only valid in the thread in which they are created. The native code must not pass local references from one thread to another.

局部引用仅在创建其的线程才有效,且必须不能够在另外一个线程内调用。

Implementing Local References

To implement local references, the Java VM creates a registry for each transition of control from Java to a native method. A registry maps nonmovable local references to Java objects, and keeps the objects from being garbage collected. All Java objects passed to the native method (including those that are returned as the results of JNI function calls) are automatically added to the registry. The registry is deleted after the native method returns, allowing all of its entries to be garbage collected.

为了实现局部引用,在JAVA到native的调用流程中,JVM创建了注册表,并将传递给native的object引用放到注册表中,注册表阻止了垃圾收集器回收对象。

当native method返回时,JVM删除对象method的注册表,并允许垃圾收集器回收对象。

There are different ways to implement a registry, such as using a table, a linked list, or a hash table. Although reference counting may be used to avoid duplicated entries in the registry, a JNI implementation is not obliged to detect and collapse duplicate entries.

 

有很多实现注册表的方式,如table、linked list、hash table。尽管引用计数能够用于避免注册重复的实体,JNI实现方不必监测和归一重复的实体。

Note that local references cannot be faithfully implemented by conservatively scanning the native stack. The native code may store local references into global or heap data structures.

Accessing Java Objects

The JNI provides a rich set of accessor functions on global and local references. This means that the same native method implementation works no matter how the VM represents Java objects internally. This is a crucial reason why the JNI can be supported by a wide variety of VM implementations.

JNI提供了大量访问全局和局部引用的函数。这意味着native method不必关心JVM内部如何表达Java对象。

The overhead of using accessor functions through opaque references is higher than that of direct access to C data structures. We believe that, in most cases, Java programmers use native methods to perform nontrivial tasks that overshadow the overhead of this interface.

通过引用调用访问函数的开销远大于直接访问C数据结构。

Accessing Primitive Arrays

This overhead is not acceptable for large Java objects containing many primitive data types, such as integer arrays and strings. (Consider native methods that are used to perform vector and matrix calculations.) It would be grossly inefficient to iterate through a Java array and retrieve every element with a function call.

对于包含许多原始数据类型的大型Java对象,例如整数数组和字符串,这种开销是不可接受的。

遍历Java数组并对每个元素调用function call将非常低效。

One solution introduces a notion of “pinning” so that the native method can ask the VM to pin down the contents of an array. The native method then receives a direct pointer to the elements. This approach, however, has two implications:

  • The garbage collector must support pinning.
  • The VM must lay out primitive arrays contiguously in memory. Although this is the most natural implementation for most primitive arrays, boolean arrays can be implemented as packed or unpacked. Therefore, native code that relies on the exact layout of boolean arrays will not be portable.

解决访问数组开销的方法是:native method获取到指向元素列表的指针,从而能指针访问到元素

We adopt a compromise that overcomes both of the above problems.

并提供了一个折中的方法

First, we provide a set of functions to copy primitive array elements between a segment of a Java array and a native memory buffer. Use these functions if a native method needs access to only a small number of elements in a large array.

首先,提供复制基础类型数组元素的函数(在java类型和native类型直接复制),通过使用这些函数,实现大数组直接的小数量复制操作

Second, programmers can use another set of functions to retrieve a pinned-down version of array elements. Keep in mind that these functions may require the Java VM to perform storage allocation and copying. Whether these functions in fact copy the array depends on the VM implementation, as follows:

  • If the garbage collector supports pinning, and the layout of the array is the same as expected by the native method, then no copying is needed.
  • Otherwise, the array is copied to a nonmovable memory block (for example, in the C heap) and the necessary format conversion is performed. A pointer to the copy is returned.

其次,程序员可以使用另一组函数来恢复数组元素。需要注意,这些函数或许需要JVM来执行分配操作和复制操作。这些函数是否复制数组依赖JVM的实现,如:

  如果垃圾收集器支持pinning,且数组和native method期望的一样,则不需要复制

  否则,数组被复制到一个不可移动的数据块中,且执行格式转换,最后返回副本的指针

Lastly, the interface provides functions to inform the VM that the native code no longer needs to access the array elements. When you call these functions, the system either unpins the array, or it reconciles the original array with its non-movable copy and frees the copy.

最后,调用函数通知JVM,native不再需要访问数组元素。当调用这些函数时,系统解锁这些数组并释放副本。

Our approach provides flexibility. A garbage collector algorithm can make separate decisions about copying or pinning for each given array. For example, the garbage collector may copy small objects, but pin the larger objects.

这种方法具有一些灵活性。

A JNI implementation must ensure that native methods running in multiple threads can simultaneously access the same array. For example, the JNI may keep an internal counter for each pinned array so that one thread does not unpin an array that is also pinned by another thread. Note that the JNI does not need to lock primitive arrays for exclusive access by a native method. Simultaneously updating a Java array from different threads leads to nondeterministic results.

JNI实现必须确保运行在多线程的native method能够并发访问同一个数组。

Accessing Fields and Methods

The JNI allows native code to access the fields and to call the methods of Java objects. The JNI identifies methods and fields by their symbolic names and type signatures. A two-step process factors out the cost of locating the field or method from its name and signature. For example, to call the method f in class cls, the native code first obtains a method ID, as follows:

jmethodID mid =      env->GetMethodID(cls, “f”, “(ILjava/lang/String;)D”);

 

The native code can then use the method ID repeatedly without the cost of method lookup, as follows:

jdouble result = env->CallDoubleMethod(obj, mid, 10, str);

 

JNI使用符号名称和类型签名来标识调用域和方法。例如,调用class cls的方法f,native需要先使用GetMethodId来获取method id,然后使用CallTypeMethod来调用对应的函数

A field or method ID does not prevent the VM from unloading the class from which the ID has been derived. After the class is unloaded, the method or field ID becomes invalid. The native code, therefore, must make sure to:

  • keep a live reference to the underlying class, or
  • recompute the method or field ID

if it intends to use a method or field ID for an extended period of time.

method id源于jvm加载的class,但是native获取的field或者method id不会阻止JVM卸载class。每当native需要访问field或者method id时,需要 保持一个底层class的有效引用;或者 重新计算method id

The JNI does not impose any restrictions on how field and method IDs are implemented internally.

Reporting Programming Errors

The JNI does not check for programming errors such as passing in NULL pointers or illegal argument types. Illegal argument types includes such things as using a normal Java object instead of a Java class object. The JNI does not check for these programming errors for the following reasons:

  • Forcing JNI functions to check for all possible error conditions degrades the performance of normal (correct) native methods.
  • In many cases, there is not enough runtime type information to perform such checking.

JNI不检查空指针、非法参数类型等类型错误。原因为:强制类型检查会降低正确情况下的性能;很多情况下,没有足够多的运行时类型信息来执行检查

Most C library functions do not guard against programming errors. The printf() function, for example, usually causes a runtime error, rather than returning an error code, when it receives an invalid address. Forcing C library functions to check for all possible error conditions would likely result in such checks to be duplicated--once in the user code, and then again in the library.

多数C库函数不检查编程错误。如printf函数,当传入无效地址时,通常会触发运行时错误,而非返回错误码。强制C库函数检查错误情况会导致重复检测:程序员调用前一次类型检查,C库函数里一次类型检查。这是低效的

The programmer must not pass illegal pointers or arguments of the wrong type to JNI functions. Doing so could result in arbitrary consequences, including a corrupted system state or VM crash.

Java Exceptions

The JNI allows native methods to raise arbitrary Java exceptions. The native code may also handle outstanding Java exceptions. The Java exceptions left unhandled are propagated back to the VM.

JNI允许native method抛出任意的java异常。native code也能处理未解决的java异常。所有未经处理的java异常将返给JVM处理。

Exceptions and Error Codes

Certain JNI functions use the Java exception mechanism to report error conditions. In most cases, JNI functions report error conditions by returning an error code and throwing a Java exception. The error code is usually a special return value (such as NULL) that is outside of the range of normal return values. Therefore, the programmer can:

  • quickly check the return value of the last JNI call to determine if an error has occurred, and
  • call a function, ExceptionOccurred(), to obtain the exception object that contains a more detailed description of the error condition.

通常,JNI函数通过函数返回错误码 或者 java异常 来报告错误。程序员通过检查返回值来确定是否发生错误,或者调用 ExceptionOccurred()来获取异常对象

There are two cases where the programmer needs to check for exceptions without being able to first check an error code:

  • The JNI functions that invoke a Java method return the result of the Java method. The programmer must call ExceptionOccurred() to check for possible exceptions that occurred during the execution of the Java method.
  • Some of the JNI array access functions do not return an error code, but may throw an ArrayIndexOutOfBoundsException or ArrayStoreException.

In all other cases, a non-error return value guarantees that no exceptions have been thrown.

Asynchronous Exceptions

In cases of multiple threads, threads other than the current thread may post an asynchronous exception. An asynchronous exception does not immediately affect the execution of the native code in the current thread, until:

  • the native code calls one of the JNI functions that could raise synchronous exceptions, or
  • the native code uses ExceptionOccurred() to explicitly check for synchronous and asynchronous exceptions.

多线程情况下,其他线程能够抛出异常异常。这些异步异常不会立即影响到当前线程的native代码执行,直到下列情形出现:当前线程的代码调用到能够抛出异步异常的代码;或者 当前线程代码调用ExceptionOccurred()显示检测同步或者异步异常

Note that only those JNI function that could potentially raise synchronous exceptions check for asynchronous exceptions.

Native methods should insert ExceptionOccurred()checks in necessary places (such as in a tight loop without other exception checks) to ensure that the current thread responds to asynchronous exceptions in a reasonable amount of time.

native method应该把 ExceptionOccurred()检查插入代码合适的地方,以确保当前线程在合适的时间响应异步异常

Exception Handling

There are two ways to handle an exception in native code:

  • The native method can choose to return immediately, causing the exception to be thrown in the Java code that initiated the native method call.
  • The native code can clear the exception by calling ExceptionClear(), and then execute its own exception-handling code.

native代码有2种方式来处理异常:立即返回异常;使用 ExceptionClear()清除异常

After an exception has been raised, the native code must first clear the exception before making other JNI calls. When there is a pending exception, the JNI functions that are safe to call are:


  ExceptionOccurred()
  ExceptionDescribe()
  ExceptionClear()
  ExceptionCheck()
  ReleaseStringChars()
  ReleaseStringUTFChars()
  ReleaseStringCritical()
  Release<Type>ArrayElements()
  ReleasePrimitiveArrayCritical()
  DeleteLocalRef()
  DeleteGlobalRef()
  DeleteWeakGlobalRef()
  MonitorExit()
  PushLocalFrame()
  PopLocalFrame()

posted on 2021-02-23 20:10  炽离  阅读(158)  评论(0编辑  收藏  举报

导航