JNI

334 阅读57分钟

简介

docs.oracle.com/javase/7/do…

Java Native Interface (JNI)

以下情况下使用jni:

  • 标准 Java 类库不支持应用程序所需的平台相关特性。

  • 您已经有一个用另一种语言编写的库,并希望通过 JNI 使其可供 Java 代码访问。

  • 您想用较低级别的语言(例如汇编)实现一小部分时间要求严格的代码。 使用jni可以实现:

  • 创建、检查和更新 Java 对象(包括数组和字符串)。

  • 调用 Java 方法。

  • 捕获并抛出异常。

  • 加载类并获取类信息。

  • 执行运行时类型检查。

设计概述

JNI 接口函数和指针

本机代码通过调用 JNI 函数来访问 Java VM 功能。JNI 函数可通过接口指针获得。接口指针是指向指针的指针。这个指针指向一个指针数组,每个指针指向一个接口函数。每个接口函数都位于数组内的预定义偏移处。

JNI 接口的组织方式类似于 C++ 虚函数表或 COM 接口。使用接口表而不是硬连接的函数条目的优点是 JNI 名称空间与本机代码分离。

JNI 接口指针只在当前线程中有效。因此,本机方法不得将接口指针从一个线程传递到另一个线程。实现 JNI 的 VM 可以在 JNI 接口指针指向的区域中分配和存储线程本地数据。

本机方法接收 JNI 接口指针作为参数。当 VM 从同一个 Java 线程多次调用本机方法时,它保证将相同的接口指针传递给本机方法。但是,本地方法可以从不同的 Java 线程调用,因此可能会接收不同的 JNI 接口指针。

编译、加载和链接本机方法

由于 Java VM 是多线程的,因此本地库也应该与支持多线程的本地编译器一起编译和链接。 使用System.loadLibrary加载本机代码,在下面的示例中,类初始化方法加载了一个特定于平台的本地库,其中f定义了本地方法:

package pkg;  

class Cls { 

     native double f(int i, String s); 

     static { 

         System.loadLibrary(“pkg_Cls”); 

     } 

} 

System.loadLibrary的参数是程序员任意选择的库名称。系统遵循标准但特定于平台的方法将库名称转换为本机库名称。例如,Solaris 系统将名称转换pkg_Clslibpkg_Cls.so,而 Win32 系统将相同的pkg_Cls名称转换为pkg_Cls.dll.

程序员还可以调用 JNI 函数RegisterNatives()来注册与类关联的本地方法。该RegisterNatives()函数对静态链接函数特别有用。

解析本机方法名称

动态链接器根据其名称解析条目。本机方法名称由以下组件连接:

  • 前缀Java_

  • 损坏的完全限定的类名

  • 下划线分隔符 ("_ ")

  • 损坏的方法名称

  • 对于重载的本机方法,两个下划线("__ ")后跟破损的参数签名

我们采用了一个简单的名称修改方案来确保所有 Unicode 字符都转换为有效的 C 函数名称。我们使用下划线(“_ ”)字符代替完全限定类名中的斜杠(“/”)。由于名称或类型描述符从不以数字开头,因此我们可以使用_0, ..._9作为转义序列,如表所示:

转义序列表示
_0XXXX一个 Unicode 字符XXXX。 请注意,小写用于 表示非 ASCII Unicode 字符,例如, _0abcd_0ABCD.
_1_
_2; 在签名中
_3[ 在签名中

本机方法参数

JNI 接口指针是本地方法的第一个参数。JNI 接口指针的类型是JNIEnv。第二个参数根据本机方法是静态的还是非静态的而有所不同。非静态本机方法的第二个参数是对对象的引用。静态本地方法的第二个参数是对其 Java 类的引用。

其余参数对应于常规 Java 方法参数。本机方法调用通过返回值将其结果传递回调用例程。

代码示例说明了使用 C 函数来实现本机方法f。本机方法f声明如下:

package pkg;  

class Cls { 

     native double f(int i, String s); 

     ... 

} 

具有长名称的 C 函数Java_pkg_Cls_f_ILjava_lang_String_2实现了本机方法f

代码示例使用 C 实现本机方法

jdouble Java_pkg_Cls_f__ILjava_lang_String_2 (
     JNIEnv *env, /* 接口指针 */
     jobject obj, /* "this" 指针 */
     jin i, / * 参数 # 1 * /
     jstring s) /* 参数 #2 */
{
     /* 获取 Java 字符串的 C 副本 */
     const char *str = (*env)->GetStringUTFChars(env, s, 0);

     /* 处理字符串 */
     ...

     /* 现在我们完成了 str */
     (*env)->ReleaseStringUTFChars(env, s, str);

     return ...
}

请注意,我们总是使用接口指针env来操作 Java 对象。

使用 C++,您可以编写更简洁的代码版本,如代码示例所示: 代码示例使用 C++ 实现本机方法:

extern "C" /* 指定 C 调用约定 */ 

jdouble Java_pkg_Cls_f__ILjava_lang_String_2 ( 

     JNIEnv *env, /* 接口指针 */ 

     jobject obj, /* "this" 指针 */ 

     jin i, / * 参数 # 1 * / 

     jstring s) /* 参数 #2 */ 

{ 

     const char *str = env->GetStringUTFChars(s, 0); 

     ... 

     env->ReleaseStringUTFChars(s, str); 

     return ... 

} 

在 C++ 中,额外的间接层级和接口指针参数从源代码中消失了。但是,底层机制与 C 完全相同。在 C++ 中,JNI 函数被定义为内联成员函数,可扩展为 C 对应项。

引用 Java 对象

原始类型(如整数、字符等)在 Java 和本机代码之间复制。另一方面,任意 Java 对象是通过引用传递的。VM 必须跟踪已传递给本机代码的所有对象,以便垃圾收集器不会释放这些对象。反过来,本机代码必须有一种方法来通知 VM 它不再需要这些对象。此外,垃圾收集器必须能够移动本机代码引用的对象。

全局和本地应用

JNI 将本机代码使用的对象引用分为两类:本地引用和全局引用。本地引用在本地方法调用期间有效,并在本地方法返回后自动释放。全局引用在被显式释放之前一直有效。

对象作为本地引用传递给本地方法。JNI 函数返回的所有 Java 对象都是本地引用。JNI 允许程序员从本地引用创建全局引用。期望 Java 对象的 JNI 函数接受全局和本地引用。本机方法可能会返回对 VM 的本地或全局引用作为其结果。

在大多数情况下,程序员应该依靠 VM 在本地方法返回后释放所有本地引用。但是,有时程序员应该显式地释放本地引用。例如,考虑以下情况:

  • 本机方法访问大型 Java 对象,从而创建对 Java 对象的本地引用。然后,本机方法在返回给调用者之前执行额外的计算。对大型 Java 对象的本地引用将防止该对象被垃圾回收,即使该对象不再用于剩余的计算中。

  • 本机方法会创建大量本地引用,尽管并非所有这些都同时使用。由于 VM 需要一定的空间来跟踪本地引用,因此创建过多的本地引用可能会导致系统内存不足。例如,本机方法循环遍历大量对象,检索元素作为本地引用,并在每次迭代时对一个元素进行操作。每次迭代后,程序员不再需要对数组元素的本地引用。

本地引用仅在创建它们的线程中有效。本机代码不得将本地引用从一个线程传递到另一个线程。

访问 Java 对象

JNI 为全局和局部引用提供了一组丰富的访问器函数。这意味着无论 VM 在内部如何表示 Java 对象,相同的本机方法实现都可以工作。这是 JNI 可以被多种 VM 实现支持的关键原因。

通过不透明引用使用访问器函数的开销高于直接访问 C 数据结构的开销。我们相信,在大多数情况下,Java 程序员使用本机方法来执行不平凡的任务,这些任务掩盖了该接口的开销。

访问字段和方法

JNI 允许本地代码访问字段并调用 Java 对象的方法。JNI 通过符号名称和类型签名来识别方法和字段。两步过程从名称和签名中排除了查找字段或方法的成本。例如调用cls类中的f方法,native 代码首先获取一个方法 ID,如下:

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

然后,本机代码可以重复使用方法 ID,而无需方法查找成本,如下所示:

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

报告编程错误

JNI 不检查编程错误,例如传入 NULL 指针或非法参数类型。非法参数类型包括使用普通 Java 对象而不是 Java 类对象等。

Java 异常

JNI 允许本地方法引发任意 Java 异常。本机代码还可以处理未解决的 Java 异常。未处理的 Java 异常会传播回 VM。

异常和错误代码

某些 JNI 函数使用 Java 异常机制来报告错误情况。在大多数情况下,JNI 函数通过返回错误代码抛出 Java 异常来报告错误情况。错误码通常是超出正常返回值范围的特殊返回值(如NULL)。因此,程序员可以:

  • 快速检查最后一次 JNI 调用的返回值以确定是否发生错误,以及

  • 调用函数 ,ExceptionOccurred()以获取包含错误条件更详细描述的异常对象。

有两种情况,程序员需要检查异常而不能先检查错误代码:

  • 调用 Java 方法的 JNI 函数返回 Java 方法的结果。程序员必须调用ExceptionOccurred()以检查 Java 方法执行期间可能发生的异常。
  • 一些 JNI 数组访问函数不返回错误代码,但可能会抛出ArrayIndexOutOfBoundsExceptionor ArrayStoreException

在所有其他情况下,非错误返回值保证没有抛出异常。

异步异常

在多线程的情况下,当前线程以外的线程可能会发布异步异常。异步异常不会立即影响当前线程中本机代码的执行,直到:

  • 本机代码调用可能引发同步异常的 JNI 函数之一,或

  • 本机代码用于ExceptionOccurred()显式检查同步和异步异常。

请注意,只有那些可能引发同步异常的 JNI 函数才检查异步异常。

本机方法应该在必要的地方插入ExceptionOccurred()检查(例如在没有其他异常检查的紧密循环中),以确保当前线程在合理的时间内响应异步异常。

异常处理

有两种方法可以处理原生代码中的异常:

  • 本机方法可以选择立即返回,从而导致在发起本机方法调用的 Java 代码中抛出异常。
  • 本机代码可以通过调用清除异常ExceptionClear(),然后执行自己的异常处理代码。

引发异常后,本机代码必须先清除异常,然后再进行其他 JNI 调用。当存在未决异常时,可以安全调用的 JNI 函数是:

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

JNI 类型和数据结构

原始类型

Java TypeNative TypeDescription
booleanjbooleanunsigned 8 bits
bytejbytesigned 8 bits
charjcharunsigned 16 bits
shortjshortsigned 16 bits
intjintsigned 32 bits
longjlongsigned 64 bits
floatjfloat32 bits
doublejdouble64 bits
voidvoidN/A

为方便起见,提供以下定义。

#define JNI_FALSE 0  
#define JNI_TRUE 1 

jsize整数类型用于描述基数索引和大小:

typedef jit jsize; 

参考类型

  • 在 C 中,所有其他 JNI 引用类型都被定义为与 jobject 相同。例如:
typedef jobject jclass; 
  • 在 C++ 中,JNI 引入了一组虚拟类来强制执行子类型关系。例如:
class _jobject {}; 
class _jclass : public _jobject {}; 
... 
typedef _jobject *jobject; 
typedef _jclass *jclass; 

字段(Field)和方法 ID

方法和字段 ID 是常规的 C 指针类型:

struct _jfieldID;              /* 不透明结构 */ 
typedef struct _jfieldID *jfieldID;   /* 字段 ID */ 
 
struct _jmethodID;              /* 不透明结构 */ 
typedef struct _jmethodID *jmethodID; /* 方法 ID */ 

值类型

用作参数数组中的元素类型。声明如下:

typedef union jvalue { 
    jboolean z; 
    jbyte    b; 
    jchar    c; 
    jshort   s; 
    jint     i; 
    jlong    j; 
    jfloat   f; 
    jdouble  d; 
    jobject  l; 
} jvalue; 

类型签名

JNI 使用 Java VM 的类型签名表示。表显示了这些类型签名。

表Java VM 类型签名

类型签名Java 类型
Zboolean
Bbyte
Cchar
Sshort
Iint
Jlong
Ffloat
Ddouble
L fully-qualified-class ;fully-qualified-class
[typetype[]
( arg-types ) ret-typemethod type

例如,Java 方法:

long f (int n, String s, int[] arr); 

具有以下类型签名:

(ILjava/lang/String;[I)J 

JNI 函数

请注意使用术语“必须”来描述对 JNI 程序员的限制。例如,当您看到某个 JNI 函数必须接收非 NULL 对象时,您有责任确保不将 NULL 传递给该 JNI 函数。因此,JNI 实现不需要在该 JNI 函数中执行 NULL 指针检查。

接口功能表

每个函数都可以通过JNIEnv参数以固定偏移量访问。JNIEnv类型是一个指向存储所有 JNI 函数指针的结构的指针。 定义如下:

typedef const struct JNINativeInterface *JNIEnv; 

VM 初始化函数表,如 代码示例所示。请注意,前三个条目是为将来与 COM 兼容而保留的。此外,我们NULL在函数表的开头附近保留了一些额外的条目,例如,将来可以在 FindClass 之后添加与类相关的 JNI 操作,而不是在表的末尾。

请注意,函数表可以在所有 JNI 接口指针之间共享。 代码示例

const struct JNINativeInterface ... = {

    NULL,
    NULL,
    NULL,
    NULL,
    GetVersion,

    DefineClass,
    FindClass,

    FromReflectedMethod,
    FromReflectedField,
    ToReflectedMethod,

    GetSuperclass,
    IsAssignableFrom,

    ToReflectedField,

    Throw,
    ThrowNew,
    ExceptionOccurred,
    ExceptionDescribe,
    ExceptionClear,
    FatalError,

    PushLocalFrame,
    PopLocalFrame,

    NewGlobalRef,
    DeleteGlobalRef,
    DeleteLocalRef,
    IsSameObject,
    NewLocalRef,
    EnsureLocalCapacity,

    AllocObject,
    NewObject,
    NewObjectV,
    NewObjectA,

    GetObjectClass,
    IsInstanceOf,

    GetMethodID,

    CallObjectMethod,
    CallObjectMethodV,
    CallObjectMethodA,
    CallBooleanMethod,
    CallBooleanMethodV,
    CallBooleanMethodA,
    CallByteMethod,
    CallByteMethodV,
    CallByteMethodA,
    CallCharMethod,
    CallCharMethodV,
    CallCharMethodA,
    CallShortMethod,
    CallShortMethodV,
    CallShortMethodA,
    CallIntMethod,
    CallIntMethodV,
    CallIntMethodA,
    CallLongMethod,
    CallLongMethodV,
    CallLongMethodA,
    CallFloatMethod,
    CallFloatMethodV,
    CallFloatMethodA,
    CallDoubleMethod,
    CallDoubleMethodV,
    CallDoubleMethodA,
    CallVoidMethod,
    CallVoidMethodV,
    CallVoidMethodA,

    CallNonvirtualObjectMethod,
    CallNonvirtualObjectMethodV,
    CallNonvirtualObjectMethodA,
    CallNonvirtualBooleanMethod,
    CallNonvirtualBooleanMethodV,
    CallNonvirtualBooleanMethodA,
    CallNonvirtualByteMethod,
    CallNonvirtualByteMethodV,
    CallNonvirtualByteMethodA,
    CallNonvirtualCharMethod,
    CallNonvirtualCharMethodV,
    CallNonvirtualCharMethodA,
    CallNonvirtualShortMethod,
    CallNonvirtualShortMethodV,
    CallNonvirtualShortMethodA,
    CallNonvirtualIntMethod,
    CallNonvirtualIntMethodV,
    CallNonvirtualIntMethodA,
    CallNonvirtualLongMethod,
    CallNonvirtualLongMethodV,
    CallNonvirtualLongMethodA,
    CallNonvirtualFloatMethod,
    CallNonvirtualFloatMethodV,
    CallNonvirtualFloatMethodA,
    CallNonvirtualDoubleMethod,
    CallNonvirtualDoubleMethodV,
    CallNonvirtualDoubleMethodA,
    CallNonvirtualVoidMethod,
    CallNonvirtualVoidMethodV,
    CallNonvirtualVoidMethodA,

    GetFieldID,

    GetObjectField,
    GetBooleanField,
    GetByteField,
    GetCharField,
    GetShortField,
    GetIntField,
    GetLongField,
    GetFloatField,
    GetDoubleField,
    SetObjectField,
    SetBooleanField,
    SetByteField,
    SetCharField,
    SetShortField,
    SetIntField,
    SetLongField,
    SetFloatField,
    SetDoubleField,

    GetStaticMethodID,

    CallStaticObjectMethod,
    CallStaticObjectMethodV,
    CallStaticObjectMethodA,
    CallStaticBooleanMethod,
    CallStaticBooleanMethodV,
    CallStaticBooleanMethodA,
    CallStaticByteMethod,
    CallStaticByteMethodV,
    CallStaticByteMethodA,
    CallStaticCharMethod,
    CallStaticCharMethodV,
    CallStaticCharMethodA,
    CallStaticShortMethod,
    CallStaticShortMethodV,
    CallStaticShortMethodA,
    CallStaticIntMethod,
    CallStaticIntMethodV,
    CallStaticIntMethodA,
    CallStaticLongMethod,
    CallStaticLongMethodV,
    CallStaticLongMethodA,
    CallStaticFloatMethod,
    CallStaticFloatMethodV,
    CallStaticFloatMethodA,
    CallStaticDoubleMethod,
    CallStaticDoubleMethodV,
    CallStaticDoubleMethodA,
    CallStaticVoidMethod,
    CallStaticVoidMethodV,
    CallStaticVoidMethodA,

    GetStaticFieldID,

    GetStaticObjectField,
    GetStaticBooleanField,
    GetStaticByteField,
    GetStaticCharField,
    GetStaticShortField,
    GetStaticIntField,
    GetStaticLongField,
    GetStaticFloatField,
    GetStaticDoubleField,

    SetStaticObjectField,
    SetStaticBooleanField,
    SetStaticByteField,
    SetStaticCharField,
    SetStaticShortField,
    SetStaticIntField,
    SetStaticLongField,
    SetStaticFloatField,
    SetStaticDoubleField,

    NewString,

    GetStringLength,
    GetStringChars,
    ReleaseStringChars,

    NewStringUTF,
    GetStringUTFLength,
    GetStringUTFChars,
    ReleaseStringUTFChars,

    GetArrayLength,

    NewObjectArray,
    GetObjectArrayElement,
    SetObjectArrayElement,

    NewBooleanArray,
    NewByteArray,
    NewCharArray,
    NewShortArray,
    NewIntArray,
    NewLongArray,
    NewFloatArray,
    NewDoubleArray,

    GetBooleanArrayElements,
    GetByteArrayElements,
    GetCharArrayElements,
    GetShortArrayElements,
    GetIntArrayElements,
    GetLongArrayElements,
    GetFloatArrayElements,
    GetDoubleArrayElements,

    ReleaseBooleanArrayElements,
    ReleaseByteArrayElements,
    ReleaseCharArrayElements,
    ReleaseShortArrayElements,
    ReleaseIntArrayElements,
    ReleaseLongArrayElements,
    ReleaseFloatArrayElements,
    ReleaseDoubleArrayElements,

    GetBooleanArrayRegion,
    GetByteArrayRegion,
    GetCharArrayRegion,
    GetShortArrayRegion,
    GetIntArrayRegion,
    GetLongArrayRegion,
    GetFloatArrayRegion,
    GetDoubleArrayRegion,
    SetBooleanArrayRegion,
    SetByteArrayRegion,
    SetCharArrayRegion,
    SetShortArrayRegion,
    SetIntArrayRegion,
    SetLongArrayRegion,
    SetFloatArrayRegion,
    SetDoubleArrayRegion,

    RegisterNatives,
    UnregisterNatives,

    MonitorEnter,
    MonitorExit,

    GetJavaVM,

    GetStringRegion,
    GetStringUTFRegion,

    GetPrimitiveArrayCritical,
    ReleasePrimitiveArrayCritical,

    GetStringCritical,
    ReleaseStringCritical,

    NewWeakGlobalRef,
    DeleteWeakGlobalRef,

    ExceptionCheck,

    NewDirectByteBuffer,
    GetDirectBufferAddress,
    GetDirectBufferCapacity,

    GetObjectRefType
  };

版本信息

获取版本

jint GetVersion(JNIEnv *env);

返回本机方法接口的版本。

  • 参数: env: JNI 接口指针。

  • 返回值: 返回高 16 位中的主要版本号和低 16 位中的次要版本号。 在 JDK/JRE 1.1 中,GetVersion()返回0x00010001.

在 JDK/JRE 1.2 中,GetVersion()返回 0x00010002.

在 JDK/JRE 1.4 中,GetVersion()返回 0x00010004.

在 JDK/JRE 1.6 中,GetVersion()返回 0x00010006.

  • 常数

    • 从 JDK/JRE 1.2 开始:
    #define JNI_VERSION_1_1 0x00010001
    #define JNI_VERSION_1_2 0x00010002
    
    /* 错误代码 */
    #define JNI_EDETACHED (-2) /* 线程与虚拟机分离 */
    #define JNI_EVERSION (-3) /* JNI 版本错误
    
    • 从 JDK/JRE 1.4 开始:
    #define JNI_VERSION_1_4 0x00010004
    
    • 从 JDK/JRE 1.6 开始:
    #define JNI_VERSION_1_6 0x00010006
    

类操作

定义类

jclass DefineClass(JNIEnv *env, const char *name, jobject loader,
const jbyte *buf, jsize bufLen);

从原始类数据的缓冲区加载一个类。在 DefineClass 调用返回后,VM 不会引用包含原始类数据的缓冲区,如果需要,它可能会被丢弃。

  • 参数: env: JNI 接口指针。

name:要定义的类或接口的名称。该字符串以修改后的 UTF-8 编码。

loader:分配给已定义类的类加载器。

buf: 包含.class文件数据的缓冲区。

bufLen: 缓冲区长度。

  • 返回值: 如果发生错误返回NULL,否则返回 Java 类对象。

  • 抛出: ClassFormatError:如果类数据未指定有效类。

ClassCircularityError: 如果一个类或接口是它自己的超类或超接口。

OutOfMemoryError: 如果系统内存不足。

SecurityException:如果调用者试图在“java”包树中定义一个类。

查找类

jclass FindClass(JNIEnv *env, const char *name);

在 JDK 1.1 版中,此函数加载一个本地定义的类。CLASSPATH它在环境变量指定的目录和 zip 文件中搜索具有指定名称的类。

从 Java 2 SDK 1.2 版开始,Java 安全模型允许非系统类加载和调用本机方法。 FindClass定位与当前本地方法关联的类加载器;即声明本机方法的类的类加载器。如果本地方法属于系统类,则不涉及类加载器。否则,将调用适当的类加载器来加载和链接命名类。

从 Java 2 SDK 1.2 版开始,当FindClass通过调用接口调用时,没有当前的本地方法或其关联的类加载器。在这种情况下,使用 的结果 ClassLoader.getSystemClassLoader。这是虚拟机为应用程序创建的类加载器,并且能够定位java.class.path 属性中列出的类。

参数name是完全限定的类名或数组类型签名。例如,类的完全限定类名java.lang.String是:

 "java/lang/String"

数组类的数组类型签名 java.lang.Object[]是:

 "[Ljava/lang/Object;"
  • 参数: env: JNI 接口指针。

name: 完全限定的类名(即包名,以“ /”分隔,后跟类名)。如果名称以“ [”(数组签名字符)开头,则返回一个数组类。该字符串以修改后的 UTF-8 编码。

  • 返回值: 从完全限定名称返回类对象,或者NULL如果找不到该类。

  • 抛出: ClassFormatError:如果类数据未指定有效类。

ClassCircularityError: 如果一个类或接口是它自己的超类或超接口。

NoClassDefFoundError:如果找不到请求的类或接口的定义。

OutOfMemoryError: 如果系统内存不足。

获取超类

jclass GetSuperclass(JNIEnv *env, jclass clazz);

如果clazz表示除Object类之外的任何类,则此函数返回表示由clazz指定的类的超类的对象。

如果clazz指定类Object,或clazz表示接口,则此函数返回 NULL

  • 参数: env: JNI 接口指针。

clazz:一个Java类对象。

  • 返回值: 返回由 clazz表示的类的超类或NULL

是否可转换

jboolean IsAssignableFrom(JNIEnv *env, jclass clazz1, jclass clazz2);

确定clazz1的对象是否可以安全地转换为clazz2

  • 参数: env: JNI 接口指针。

clazz1: 第一个参数类

clazz2: 第二个参数类

  • 返回值: 如果以下任一条件为真,则返回JNI_TRUE

    • 第一个和第二个类参数引用同一个 Java 类。

    • 第一类是第二类的子类。

    • 第一个类将第二个类作为其接口之一。

异常

Throw

jint Throw(JNIEnv *env, jthrowable obj);

导致java.lang.Throwable对象被抛出。

  • 参数: env: JNI 接口指针。

obj: 一个java.lang.Throwable对象。

  • 返回值: 成功返回 0;失败的负值。

ThrowNew

jint ThrowNew(JNIEnv *env, jclass clazz, const char *message);

使用message指定的消息从指定的类构造一个异常对象,并引发该异常。

  • 参数: env: JNI 接口指针。

clazzjava.lang.Throwable 的子类。

message:用于构造java.lang.Throwable对象的消息。该字符串以修改后的 UTF-8 编码。

  • 返回值: 成功返回 0;失败返回负值。

  • 抛出: 新构造的java.lang.Throwable对象。

发生异常

jthrowable ExceptionOccurred(JNIEnv *env);

确定是否抛出异常。在本地代码调用 ExceptionClear()或 Java 代码处理异常之前,异常一直被抛出。

  • 参数: env: JNI 接口指针。

  • 返回值: 返回当前正在抛出的异常对象,或者NULL如果当前没有抛出异常。

异常描述

void ExceptionDescribe(JNIEnv *env);

将堆栈的异常和回溯打印到系统错误报告通道,例如stderr. 这是为调试提供的便利例程。

  • 参数: env: JNI 接口指针。

异常清除

void ExceptionClear(JNIEnv *env);

清除当前抛出的任何异常。如果当前没有抛出异常,则此例程无效。

  • 参数: env: JNI 接口指针。

致命错误

void FatalError(JNIEnv *env, const char *msg);

引发致命错误并且不希望 VM 恢复。此函数不返回。

  • 参数: env: JNI 接口指针。

msg: 错误信息。该字符串以修改后的 UTF-8 编码。

异常检查

jboolean ExceptionCheck(JNIEnv *env);

我们引入了一个方便的函数来检查未决异常,而无需创建对异常对象的本地引用。

JNI_TRUE当有未决异常时返回;否则,返回JNI_FALSE

全局和本地引用

全局引用

创建全局引用

jobject NewGlobalRef(JNIEnv *env, jobject obj);

创建对参数obj引用的对象的新全局引用。obj参数可以是全局或本地引用。 全局引用必须通过调用显式DeleteGlobalRef()处理。

  • 参数: env: JNI 接口指针。

obj: 全局或本地引用。

  • 返回值: 返回一个全局引用,或者如果系统内存不足返回NULL

删除全局引用

void DeleteGlobalRef(JNIEnv *env, jobject globalRef);

删除globalRef指向的全局引用 。

  • 参数: env: JNI 接口指针。

globalRef: 全局引用。

本地引用

本地引用在本机方法调用期间有效。在本机方法返回后,它们会自动释放。每个本地引用都会消耗一定数量的 Java 虚拟机资源。程序员需要确保本地方法不会过度分配本地引用。虽然本地方法返回 Java 后会自动释放本地引用,但是过多分配本地引用可能会导致 VM 在本地方法执行期间内存不足。

删除LocalRef

void DeleteLocalRef(JNIEnv *env, jobject localRef);

删除localRef指向的本地引用 。

  • 参数: env: JNI 接口指针。

localRef: 本地参考。

笔记

JDK/JRE 1.1 提供了上述DeleteLocalRef功能,以便程序员可以手动删除本地引用。例如,如果本机代码遍历一个可能很大的对象数组并在每次迭代中使用一个元素,在下一次迭代中创建新的局部引用之前,删除对不再使用的数组元素的局部引用是一种很好的做法。

从 JDK/JRE 1.2 开始,为本地引用生命周期管理提供了一组额外的函数。它们是下面列出的四个函数。

确保本地容量

jint EnsureLocalCapacity(JNIEnv *env, jint capacity);

确保可以在当前线程中创建至少给定数量的本地引用。 成功返回 0;否则返回负数并抛出 OutOfMemoryError.

在进入本地方法之前,VM 会自动确保至少可以创建16 个本地引用。

为了向后兼容,VM 分配的本地引用超出了保证的容量。(作为调试支持,VM 可能会警告用户正在创建过多的本地引用。在 JDK 中,程序员可以提供 -verbose:jni命令行选项来打开这些消息。)如果不能在保证的容量之外创建更多本地引用,VM将调用FatalError

PushLocalFrame

jint PushLocalFrame(JNIEnv *env, jint capacity);

创建一个新的局部参考系,其中至少可以创建给定数量的局部参考。成功时返回 0,失败时返回负数和挂起OutOfMemoryError

请注意,已在先前本地框架中创建的本地参考在当前本地框架中仍然有效。

PopLocalFrame

jobject PopLocalFrame(JNIEnv *env, jobject result);

弹出当前本地引用框架,释放所有本地引用,并在给定result对象的前一个本地引用框架中返回一个本地引用。

如果不需要返回对前一帧的引用,则传递NULL作为结果。

创建本地引用

jobject NewLocalRef(JNIEnv *env, jobject ref);

创建一个新的本地引用,该引用引用与ref相同的对象 。给定的ref可能是全局或本地引用。NULL如果ref 引用则返回null

弱全局参考

弱全局引用是一种特殊的全局引用。与普通的全局引用不同,弱全局引用允许对底层 Java 对象进行垃圾收集。在使用全局或局部引用的任何情况下都可以使用弱全局引用。当垃圾收集器运行时,如果对象仅由弱引用引用,它会释放底层对象。指向已释放对象的弱全局引用在功能上等同于NULL. 程序员可以通过IsSameObject将弱引用与NULL.

JNI 中的弱全局引用是 Java 弱引用的简化版本,作为 Java 2 平台 API(java.lang.ref包及其类)的一部分提供。 澄清 由于垃圾收集可能在本机方法运行时发生,因此弱全局引用引用的对象可以随时释放。虽然在使用全局引用的情况下可以使用弱全局引用,但这样做通常是不合适的,因为它们可能在未经通知的情况下在功能上等同于NULL。

虽然IsSameObject可用于确定弱全局引用是否引用已释放的对象,但它不会阻止对象在此后立即被释放。因此,程序员可能不会依赖此检查来确定在将来的任何JNI函数调用中是否可以使用弱全局引用(作为非空引用)。

为了克服这一固有限制,建议使用JNI函数NewLocalRef或NewGlobalRef获取对同一对象的标准(强)局部或全局引用,并使用该强引用访问预期对象。如果对象已被释放,这些函数将返回NULL,否则将返回强引用(这将阻止对象被释放)。当不再需要立即访问对象时,应该显式删除新引用,从而释放对象。

弱全局引用比其他类型的弱引用(SoftReference或WeakReference类的Java对象)弱。在SoftReference或WeakReference对象对同一特定对象的引用被清除之前,对特定对象的弱全局引用在功能上不会等同于NULL。

弱全局引用弱于Java对需要终结的对象的内部引用。弱全局引用在被引用对象(如果存在)的终结器完成之前,在功能上不会等同于NULL。

弱全局引用和幻影引用之间的交互未定义。特别是,Java VM的实现可能(或可能不)在PhantomReference之后处理弱全局引用,并且可能(或可能不)使用弱全局引用来保留PhantomReference对象也引用的对象。应该避免使用这种未定义的弱全局引用。

创建弱全局引用

jweak NewWeakGlobalRef(JNIEnv *env, jobject obj);

创建一个新的弱全局引用。 如果obj引用null,或者 VM 内存不足,则返回NULL 。如果 VM 内存不足, OutOfMemoryError则会抛出。

删除弱全局引用

void DeleteWeakGlobalRef(JNIEnv *env, jweak obj);

删除给定弱全局引用所需的 VM 资源。

对象操作

分配对象

jobject AllocObject(JNIEnv *env, jclass clazz);

分配一个新的 Java 对象,而不调用该对象的任何构造函数。返回对对象的引用。

clazz 参数不能引用数组类。

  • 参数: env: JNI 接口指针。

clazz:一个Java类对象。

  • 返回值: 返回一个 Java 对象,或者NULL如果该对象无法构造。

  • 抛出: InstantiationException:如果类是接口或抽象类。

OutOfMemoryError: 如果系统内存不足。

NewObject NewObjectA NewObjectV

构造一个新的 Java 对象。方法 ID 指示要调用的构造方法。此 ID 必须通过调用GetMethodID()with <init>作为方法名称和 voidV) 作为返回类型来获取。 clazz参数不得引用数组类。

  • 参数: env: JNI 接口指针。

clazz:一个Java类对象。

methodID: 构造函数的方法 ID。

  • 返回值: 返回一个 Java 对象,或者NULL如果该对象无法构造。

  • 抛出: InstantiationException:如果类是接口或抽象类。

OutOfMemoryError: 如果系统内存不足。

构造函数抛出的任何异常。

创建对象

jobject NewObject(JNIEnv *env, jclass clazz, jmethodID methodID, ...);

程序员将所有要传递给构造函数的参数紧跟在methodID参数之后。NewObject()接受这些参数并将它们传递给程序员希望调用的 Java 方法。

  • 参数: 构造函数的参数。

创建对象

jobject NewObjectA(JNIEnv *env, jclass clazz,jmethodID methodID, const jvalue *args);

程序员将所有要传递给构造函数的参数放在紧跟参数的args 数组中。 接受此数组中的参数,然后将它们传递给程序员希望调用的 Java 方法。

  • 参数: args: 构造函数的参数数组。

创建对象

jobject NewObjectV(JNIEnv *env, jclass clazz, jmethodID methodID, va_list args);

程序员将所有要传递给构造函数的参数放在紧跟在参数args 后面的类型 参数中。接受这些参数,然后将它们传递给程序员希望调用的 Java 方法。va_list``methodID``NewObjectV()

  • 参数: args: 构造函数的参数 va_list。

获取对象类

jclass GetObjectClass(JNIEnv *env, jobject obj);

返回对象的类。

  • 参数: env: JNI 接口指针。

obj: 一个 Java 对象(不能是NULL)。

  • 返回值: 返回一个 Java 类对象。

获取对象引用类型

jobjectRefType GetObjectRefType(JNIEnv* env, jobject obj);

返回 obj参数引用的对象的类型。参数obj可以是本地、全局或弱全局引用。

  • 参数: env: JNI 接口指针。

obj:本地、全局或弱全局引用。

  • 返回值:

该函数GetObjectRefType返回以下定义为jobjectRefType的枚举值之一:

JNIInvalidRefType = 0,
JNILocalRefType = 1,
JNIGlobalRefType = 2,
JNIWeakGlobalRefType = 3

如果参数obj是弱全局引用类型,则返回JNIWeakGlobalRefType.

如果参数obj是全局引用类型,则返回值为JNIGlobalRefType.

如果参数obj是本地引用类型,则返回值为JNILocalRefType.

如果obj参数不是有效引用,则此函数的返回值为JNIInvalidRefType.

无效引用是不是有效句柄的引用。也就是说,obj指针地址不指向内存中已从 Ref 创建函数之一分配或从 JNI 函数返回的位置。

因此,NULL将是一个无效的引用并GetObjectRefType(env,NULL) 返回JNIInvalidRefType.

另一方面,空引用(指向空的引用)将返回空引用最初创建时的引用类型。

GetObjectRefType不能用于已删除的引用。

由于引用通常被实现为指向内存数据结构的指针,这些数据结构可能被 VM 中的任何引用分配服务重用,因此一旦删除,未指定GetObjectRefType将返回的值。

是否是类的实例

jboolean IsInstanceOf(JNIEnv *env, jobject obj, jclass clazz);

测试对象是否是类的实例。

  • 参数: env: JNI 接口指针。

obj:一个Java对象。

clazz:一个Java类对象。

  • 返回值: 返回JNI_TRUE是否 obj可以强制转换为clazz; 否则,返回JNI_FALSE。一个NULL对象可以转换为任何类。

是否同一个对象

jboolean IsSameObject(JNIEnv *env, jobject ref1, jobject ref2);

测试两个引用是否引用同一个 Java 对象。

  • 参数: env: JNI 接口指针。

ref1:一个Java对象。

ref2:一个Java对象。

  • 返回值: 返回JNI_TRUEif ref1ref2 引用同一个 Java 对象,或者两者都是NULL;否则,返回JNI_FALSE

访问对象的字段

获取字段ID

jfieldID GetFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig);

返回类的实例(非静态)字段的字段 ID。该字段由其名称和签名指定。访问器函数的Get<type>FieldSet<type>Field系列使用字段 ID 来检索对象字段。

GetFieldID()导致一个未初始化的类被初始化。

GetFieldID()不能用于获取数组的长度字段。改为使用GetArrayLength()

  • 参数: env: JNI 接口指针。

clazz:一个Java类对象。

name: 以 0 结尾的修改后的 UTF-8 字符串中的字段名称。

sig: 以 0 结尾的修改过的 UTF-8 字符串中的字段签名。

  • 返回值: 返回字段 ID,或者NULL如果操作失败。

  • 抛出: NoSuchFieldError: 如果找不到指定的字段。

ExceptionInInitializerError: 如果类初始化程序由于异常而失败。

OutOfMemoryError: 如果系统内存不足。

Get<type>Field Routines

NativeType Get<type>Field(JNIEnv *env, jobject obj, jfieldID fieldID);

这一系列访问器例程返回对象的实例(非静态)字段的值。要访问的字段由调用GetFieldID()获得的字段 ID 指定。

下表描述了 Get<type>Field 例程名称和结果类型。您应该将Get<type>Field 中的type 替换为该字段的 Java 类型,或使用表中的实际例程名称之一,并将NativeType替换为该例程的相应本机类型。 表Get<type>Field 系列的访问器例程

Get<type>Field Routine NameNative Type
GetObjectField()jobject
GetBooleanField()jboolean
GetByteField()jbyte
GetCharField()jchar
GetShortField()jshort
GetIntField()jint
GetLongField()jlong
GetFloatField()jfloat
GetDoubleField()jdouble

Get<type>Field 系列的访问器例程

Get<type>Field Routine Nameindex
GetObjectField()95
GetBooleanField()96
GetByteField()97
GetCharField()98
GetShortField()99
GetIntField()100
GetLongField()101
GetFloatField()102
GetDoubleField()103
  • 参数: env: JNI 接口指针。

obj: 一个 Java 对象(不能是NULL)。

fieldID: 一个有效的字段 ID。

  • 返回值: 返回字段的内容。

设置字段ID的值

void Set<type>Field  (JNIEnv *env, jobject obj, jfieldID fieldID, NativeType value);

这一系列访问器例程设置对象的实例(非静态)字段的值。要访问的字段由调用 获得的字段 ID 指定GetFieldID()

下表描述了Set<type>Field 例程名称和值类型。您应该将Set<type>Field中 的type 替换为该字段的 Java 类型,或者使用表中的实际例程名称之一,并将 NativeType 替换为该例程的相应本机类型。

Set<type>Field 系列的访问器例程

Set<type>Field RoutineNative Type
SetObjectField()jobject
SetBooleanField()jboolean
SetByteField()jbyte
SetCharField()jchar
SetShortField()jshort
SetIntField()jint
SetLongField()jlong
SetFloatField()jfloat
SetDoubleField()jdouble

Set<type>Field 系列的访问器例程

Set<type>Field 指数
SetObjectField()104
SetBooleanField()105
SetByteField()106
SetCharField()107
SetShortField()108
SetIntField()109
SetLongField()110
SetFloatField()111
SetDoubleField()112
  • 参数: env: JNI 接口指针。

obj: 一个 Java 对象(不能是NULL)。

fieldID: 一个有效的字段 ID。

value: 字段的新值。

调用实例方法

获取方法ID

jmethodID GetMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig);

返回类或接口的实例(非静态)方法的方法 ID。该方法可以在clazz的超类之一中定义并由 继承clazz。该方法由其名称和签名确定。

GetMethodID()导致一个未初始化的类被初始化。

要获取构造函数的方法 ID,请提供 <init>方法名称和 voidV) 作为返回类型。

  • 参数: env: JNI 接口指针。

clazz:一个Java类对象。

name: 以 0 结尾的修改后的 UTF-8 字符串中的方法名称。

sig: 以 0 结尾的修改过的 UTF-8 字符串中的方法签名。

  • 返回值: NULL如果找不到指定的方法,则返回方法 ID 。

  • 抛出:

NoSuchMethodError: 如果找不到指定的方法。

ExceptionInInitializerError: 如果类初始化程序由于异常而失败。

OutOfMemoryError: 如果系统内存不足。

调用方法

NativeType Call<type>Method(JNIEnv *env, jobject obj, jmethodID methodID, ...);

NativeType Call<type>MethodA (JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args);

NativeType Call<type>MethodV (JNIEnv *env, jobject obj, jmethodID methodID, va_list args);

这三个操作系列中的方法用于从本地方法调用 Java 实例方法。它们仅在将参数传递给它们调用的方法的机制上有所不同。

这些操作系列根据指定的方法 ID 调用 Java 对象上的实例(非静态)方法。methodID参数必须通过调用来获得GetMethodID()

当这些函数用于调用私有方法和构造函数时,方法 ID 必须从 的真实类派生obj,而不是从其超类之一派生。

Call<type>Method Routines

程序员将所有要传递给方法的参数紧跟在methodID参数之后。Call<type>Method例程接受这些参数并将它们传递给程序员希望调用的 Java 方法。

Call<type>MethodA Routines

程序员将该方法的所有参数放在methodID参数后面的JValue的args数组中。 Call<type>MethodA例程接受这个数组中的参数,然后将它们传递给程序员希望调用的Java方法。

Call<type>MethodV Routines

程序员将该方法的所有参数放在紧跟methodID参数之后的va_list类型的args参数中。Call<type>MethodV例程接受参数,然后将它们传递给程序员希望调用的Java方法。

下表根据结果类型描述了每个方法调用例程。您应该将 Call<type>Method中的type 替换为您正在调用的方法的 Java 类型(或使用表中的实际方法调用例程名称之一),并将NativeType替换为该例程的相应本机类型。

Call<type>Method Routine NameNative Type
CallVoidMethod() CallVoidMethodA() CallVoidMethodV()void
CallObjectMethod() CallObjectMethodA() CallObjectMethodV()jobject
CallBooleanMethod() CallBooleanMethodA() CallBooleanMethodV()jboolean
CallByteMethod() CallByteMethodA() CallByteMethodV()jbyte
CallCharMethod() CallCharMethodA() CallCharMethodV()jchar
CallShortMethod() CallShortMethodA() CallShortMethodV()jshort
CallIntMethod() CallIntMethodA() CallIntMethodV()jint
CallLongMethod() CallLongMethodA() CallLongMethodV()jlong
CallFloatMethod() CallFloatMethodA() CallFloatMethodV()jfloat
CallDoubleMethod() CallDoubleMethodA() CallDoubleMethodV()jdouble
  • 参数: env: JNI 接口指针。

obj:一个Java对象。

methodID: 方法 ID。

  • Call<type>Method 例程的附加参数: Java 方法的参数。

  • Call<type>MethodA 例程的附加参数: args: 参数数组。

  • Call<type>MethodV 例程的附加参数: args: 参数的 va_list。

  • 返回值: 返回调用 Java 方法的结果。

调用java方法的结果

NativeType CallNonvirtual<type>Method(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, ...); 

NativeType CallNonvirtual<type>MethodA (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, const jvalue *args);

NativeType CallNonvirtual<type>MethodV(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, va_list args);

访问静态字段

获取静态字段 ID

jfieldID GetStaticFieldID(JNIEnv *env, jclass clazz,const char *name, const char *sig);

返回类的静态字段的字段 ID。该字段由其名称和签名指定。访问器函数的GetStatic<type>Field和 SetStatic<type>Field系列使用字段 ID 来检索静态字段。

GetStaticFieldID() 导致一个未初始化的类被初始化。

  • 参数: env: JNI 接口指针。

clazz:一个Java类对象。

name: 以 0 结尾的修改后的 UTF-8 字符串中的静态字段名称。

sig: 以 0 结尾的修改过的 UTF-8 字符串中的字段签名。

  • 返回值: NULL如果找不到指定的静态字段,则返回字段 ID 。

  • 抛出: NoSuchFieldError: 如果找不到指定的静态字段。

ExceptionInInitializerError: 如果类初始化程序由于异常而失败。

OutOfMemoryError: 如果系统内存不足。

获取静态字段 ID的值

NativeType GetStatic<type>Field(JNIEnv *env, jclass clazz, jfieldID fieldID);

这一系列访问器例程返回对象的静态字段的值。要访问的字段由字段 ID 指定,该 ID 通过调用 获取GetStaticFieldID()

  • 参数: env: JNI 接口指针。

clazz:一个Java类对象。

fieldID:静态字段ID。

  • 返回值: 返回静态字段的内容。

设置静态字段的值

void SetStatic<type>Field(JNIEnv *env, jclass clazz, jfieldID fieldID, NativeType value);

这一系列访问器例程设置对象的静态字段的值。要访问的字段由字段 ID 指定,该 ID 通过调用GetStaticFieldID()获取。

  • 参数: env: JNI 接口指针。

clazz:一个Java类对象。

fieldID:静态字段ID。

value: 字段的新值。

调用静态方法

获取静态方法 ID

jmethodID GetStaticMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig);

返回类的静态方法的方法 ID。该方法由其名称和签名指定。

GetStaticMethodID() 导致一个未初始化的类被初始化。

  • 参数: env: JNI 接口指针。

clazz:一个Java类对象。

name: 以 0 结尾的修改后的 UTF-8 字符串中的静态方法名称。

sig: 以 0 结尾的修改后的 UTF-8 字符串中的方法签名。

  • 返回值: 返回方法 ID,或者NULL如果操作失败。

  • 抛出: NoSuchMethodError: 如果找不到指定的静态方法。

ExceptionInInitializerError: 如果类初始化程序由于异常而失败。

OutOfMemoryError: 如果系统内存不足。

获取静态方法 ID的值

NativeType CallStatic<type>Method(JNIEnv *env, jclass clazz, jmethodID methodID, ...);

NativeType CallStatic<type>MethodA(JNIEnv *env, jclass clazz, jmethodID methodID, jvalue *args);

NativeType CallStatic<type>MethodV(JNIEnv *env, jclass clazz, jmethodID methodID, va_list args);

这一系列操作根据指定的方法 ID 调用 Java 对象的静态方法。methodID参数必须通过调用来获得 GetStaticMethodID``()

方法 ID 必须派生自clazz,而不是其超类之一。

  • 参数: env: JNI 接口指针。

clazz:一个Java类对象。

methodID: 静态方法 ID。

  • 返回值: 返回调用静态 Java 方法的结果。

字符串操作

创建字符串

jstring NewString(JNIEnv *env, const jchar *unicodeChars,jsize len);

java.lang.String从 Unicode 字符数组构造一个新对象。

  • 参数: env: JNI 接口指针。

unicodeChars: 指向 Unicode 字符串的指针。

len: Unicode 字符串的长度。

  • 返回值: 返回一个 Java 字符串对象,或者NULL如果该字符串无法构造。

  • 抛出: OutOfMemoryError: 如果系统内存不足。

获取字符串长度

jsize GetStringLength(JNIEnv *env, jstring string);

返回 Java 字符串的长度(Unicode 字符的计数)。

  • 参数: env: JNI 接口指针。

string:一个Java字符串对象。

  • 返回值: 返回 Java 字符串的长度。

获取字符串字符

const jchar * GetStringChars(JNIEnv *env, jstring string,jboolean *isCopy);

返回指向字符串的 Unicode 字符数组的指针。这个指针在ReleaseStringchars()被调用之前是有效的。

如果isCopy不是 NULL,则*isCopy设置为JNI_TRUE是否进行了复制;或者 JNI_FALSE如果没有复制,则设置为。

  • 参数: env: JNI 接口指针。

string:一个Java字符串对象。

isCopy: 指向布尔值的指针。

  • 返回值: NULL如果操作失败,则返回指向 Unicode 字符串的指针 。

释放字符串字符

void ReleaseStringChars(JNIEnv *env, jstring string, const jchar *chars);

通知 VM 本机代码不再需要访问charschars参数是从 string获得的指针GetStringChars()

参数: env: JNI 接口指针。

string:一个Java字符串对象。

chars: 指向 Unicode 字符串的指针。

创建字符串UTF

jstring NewStringUTF(JNIEnv *env, const char *bytes);

java.lang.String从经过修改的 UTF-8 编码的字符数组构造一个新对象。

  • 参数: env: JNI 接口指针。

bytes:指向修改后的 UTF-8 字符串的指针。

  • 返回值: 返回一个 Java 字符串对象,或者NULL如果该字符串无法构造。

  • 抛出: OutOfMemoryError: 如果系统内存不足。

获取字符串UTF长度

jsize GetStringUTFLength(JNIEnv *env, jstring string);
  • 参数: env: JNI 接口指针。

string:一个Java字符串对象。

  • 返回值: 返回字符串的 UTF-8 长度。

获取字符串UTFChars

const char * GetStringUTFChars(JNIEnv *env, jstring string, jboolean *isCopy);

返回一个指向字节数组的指针,该数组表示经过修改的 UTF-8 编码的字符串。这个数组在被释放之前是有效的ReleaseStringUTFChars()

如果isCopy不是 NULL,则*isCopy设置为JNI_TRUE是否进行了复制;或者 如果没有复制,则设置为JNI_FALSE

  • 参数: env: JNI 接口指针。

string:一个Java字符串对象。

isCopy: 指向布尔值的指针。

  • 返回值: NULL如果操作失败,则返回指向修改后的 UTF-8 字符串的指针 。

释放字符串UTF字符

void ReleaseStringUTFChars(JNIEnv *env, jstring string, const char *utf);

通知 VM 本机代码不再需要访问utfutf参数是从stringusing派生的指针GetStringUTFChars()

  • 参数: env: JNI 接口指针。

string:一个Java字符串对象。

utf: 指向修改后的 UTF-8 字符串的指针。

获取字符串区域

void GetStringRegion(JNIEnv *env, jstring str, jsize start, jsize len, jchar *buf);

len将从 offset 开始的 Unicode 字符数复制start到给定的缓冲区 buf

引发StringIndexOutOfBoundsException索引溢出。

获取字符串UTFRegion

void GetStringUTFRegion(JNIEnv *env, jstring str, jsize start, jsize len, char *buf);

len将从偏移量开始的 Unicode 字符数转换start为修改后的 UTF-8 编码,并将结果放在给定的缓冲区buf中。

引发StringIndexOutOfBoundsException索引溢出。

获取/释放字符串字符

const jchar * GetStringCritical(JNIEnv *env, jstring string, jboolean *isCopy);
void ReleaseStringCritical(JNIEnv *env, jstring string, const jchar *carray);

这两个函数的语义类似于现有的Get/ReleaseStringChars函数。如果可能,VM返回一个指向字符串元素的指针;否则,将制作一份副本。然而,这些功能的使用方式有很大的限制。在由Get/ReleaseStringCritical调用包围的代码段中,本机代码不得发出任意JNI调用,或导致当前线程阻塞。

Get/ReleaseStringCritical上的限制与Get/ReleasePrimitiveArrayCritical上的限制类似。

数组操作

获取数组长度

jsize GetArrayLength(JNIEnv *env, jarray array);

返回数组中元素的数量。

  • 参数: env: JNI 接口指针。

array:一个Java数组对象。

  • 返回值: 返回数组的长度。

创建对象数组

jobjectArray NewObjectArray(JNIEnv *env, jsize length, jclass elementClass, jobject initialElement);

构造一个保存类中对象的新数组 elementClass。所有元素最初都设置为initialElement.

  • 参数: env: JNI 接口指针。

length: 数组大小。

elementClass: 数组元素类。

initialElement: 初始化值。

  • 返回值: 返回一个 Java 数组对象,或者NULL如果数组无法构造。

  • 抛出: OutOfMemoryError: 如果系统内存不足。

获取对象数组元素

jobject GetObjectArrayElement(JNIEnv *env,jobjectArray array, jsize index);

返回Object数组的一个元素。

  • 参数: env: JNI 接口指针。

array:一个Java数组。

index: 数组索引。

  • 返回值: 返回一个 Java 对象。

  • 抛出: ArrayIndexOutOfBoundsException: 如果index没有在数组中指定有效索引。

设置对象数组元素

void SetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index, jobject value);

设置Object数组的一个元素。

  • 参数: env: JNI 接口指针。

array:一个Java数组。

index: 数组索引。

value: 新值。

  • 抛出: ArrayIndexOutOfBoundsException: 如果index没有在数组中指定有效索引。

ArrayStoreException: 如果类value不是数组元素类的子类。

创建原始数组

*ArrayType* *New<PrimitiveType>Array*`(JNIEnv *env, jsize length);

用于构造新的原始数组对象的一系列操作。

  • 参数: env: JNI 接口指针。

length: 数组长度。

  • 返回值: 返回一个 Java 数组,或者NULL如果该数组无法构造。

获得原始数组的函数

NativeType  *Get<PrimitiveType>ArrayElements(JNIEnv *env, ArrayType array, jboolean *isCopy);

返回原始数组主体的一系列函数。

返回基元数组主体的函数族。在调用相应的Release<PrimitiveType>ArrayElements()函数之前,结果是有效的。由于返回的数组可能是Java数组的副本,因此在调用Release<PrimitiveType>ArrayElements()之前,对返回数组所做的更改不一定会反映在原始数组中。

如果isCopy不为NULL,那么如果制作了副本,*isCopy设置为JNI_TRUE;或者,如果没有复制,则设置为JNI_FALSE。

下表介绍了特定的基元数组元素访问器。你应该做以下替换:

用表中的一个实际基元元素访问器例程名称替换Get<PrimitiveType>ArrayElements

用相应的数组类型替换ArrayType。

用该例程的相应本机类型替换NativeType。

无论Java VM中如何表示布尔数组,GetBooleanArrayElements()始终返回指向jbooleans的指针,每个字节表示一个元素(未打包的表示)。所有其他类型的数组都保证在内存中是连续的。

  • 参数: env: JNI 接口指针。

array:一个Java字符串对象。

isCopy: 指向布尔值的指针。

  • 返回值: 返回指向数组元素的指针,或者 NULL如果操作失败。

释放原始数组的函数

void Release<PrimitiveType>ArrayElements (JNIEnv *env, ArrayType array, NativeType*  elems, jint mode);

通知 VM 本机代码不再需要访问的一系列函数elems。该参数是使用相应的Get PrimitiveType ArrayElements()  函数elems派生的指针。如有必要,此函数会将对原始数组所做的所有更改复制回来。

mode参数提供有关如何释放数组缓冲区的信息。 mode如果elems不是 中元素的副本,则无效array。否则,mode 会产生如下影响,如下表所示:

表 4-10 原始阵列释放模式

modeactions
0复制内容并释放 elems缓冲区
JNI_COMMIT复制回内容但不释放 elems缓冲区
JNI_ABORT释放缓冲区而不复制可能的更改

在大多数情况下,程序员将“0”传递给mode参数以确保固定和复制数组的行为一致。其他选项使程序员可以更好地控制内存管理,并且应该非常小心地使用。

  • 参数: env: JNI 接口指针。

array:一个Java数组对象。

elems: 指向数组元素的指针。

mode: 释放模式。

获取原始数组的区域

void Get<PrimitiveType>ArrayRegion(JNIEnv *env,ArrayType array,jsize start, jsize len, NativeType *buf);

将原始数组的一个区域复制到缓冲区中的一系列函数。

  • 参数: env: JNI 接口指针。

array:一个Java数组。

start: 起始索引。

len:要复制的元素数。

buf:目标缓冲区。

  • 抛出: ArrayIndexOutOfBoundsException: 如果区域中的索引之一无效。

设置原始数组的区域

void Set<PrimitiveType>ArrayRegion (JNIEnv *env, ArrayType  const  NativeType array, jsize start, jsize len, *buf);

从缓冲区复制回原始数组区域的一系列函数。

  • 参数: env: JNI 接口指针。

array:一个Java数组。

start: 起始索引。

len:要复制的元素数。

buf: 源缓冲区。

  • 抛出: ArrayIndexOutOfBoundsException: 如果区域中的索引之一无效。

获取/释放原始数组的区域

void * GetPrimitiveArrayCritical(JNIEnv *env, jarray array, jboolean *isCopy);
void ReleasePrimitiveArrayCritical(JNIEnv *env, jarray array, void *carray, jint mode);

这两个函数的语义与现有的Get/Release<primitivetype>ArrayElements函数非常相似。如果可能,VM返回一个指向基元数组的指针;否则,将制作一份副本。然而,这些功能的使用方式有很大的限制。

在调用GetPrimitiveArrayCritical之后,本机代码在调用ReleasePrimitiveArrayCritical之前不应运行较长时间。我们必须将这对函数中的代码视为在“关键区域”中运行在关键区域内,本机代码不得调用其他JNI函数,或任何可能导致当前线程阻塞并等待另一个Java线程的系统调用。(例如,当前线程不能对另一个Java线程正在编写的流调用读取。)

这些限制使得本机代码更有可能获得阵列的未复制版本,即使VM不支持固定。例如,当本机代码持有指向通过GetPrimitiveArrayCritical获得的数组的指针时,VM可能会暂时禁用垃圾收集。

可以嵌套多对GetPrimitiveArrayCritical和ReleasePrimitiveArrayCritical。例如:

jint len = (*env)->GetArrayLength(env, arr1);
  jbyte *a1 = (*env)->GetPrimitiveArrayCritical(env, arr1, 0);
  jbyte *a2 = (*env)->GetPrimitiveArrayCritical(env, arr2, 0);
  /* 我们需要检查一下,以防虚拟机试图复制。*/
  if (a1 == NULL || a2 == NULL) {
    ... /* 引发内存不足异常 */
  }
  memcpy(a1, a2, len);
  (*env)->ReleasePrimitiveArrayCritical(env, arr2, a2, 0);
  (*env)->ReleasePrimitiveArrayCritical(env, arr1, a1, 0);

注册本机方法

注册本机方法

jint RegisterNatives(JNIEnv *env, jclass clazz, const JNINativeMethod *methods, jint nMethods);

使用参数指定的类注册本机方法clazz。该methods参数指定一个结构数组,JNINativeMethod其中包含本机方法的名称、签名和函数指针。JNINativeMethod 结构的 namesignature字段是指向修改后的 UTF-8 字符串的指针。该nMethods参数指定数组中本机方法的数量。JNINativeMethod结构定义如下:

typedef struct { 

    char *name; 

    char *signature; 

    void *fnPtr; 

} JNINativeMethod; 

函数指针名义上必须具有以下签名:

ReturnType (*fnPtr)(JNIEnv *env, jobject objectOrClass, ...); 
  • 参数: env: JNI 接口指针。

clazz:一个Java类对象。

methods:类中的本机方法。

nMethods:类中本机方法的数量。

  • 返回值: 成功返回“0”;失败时返回负值。

  • 抛出: NoSuchMethodError:如果找不到指定的方法或该方法不是本机的。

注销本地方法

jint UnregisterNatives(JNIEnv *env, jclass clazz);

注销类的本机方法。该类返回到它与它的本地方法函数链接或注册之前的状态。

此函数不应在正常的本机代码中使用。相反,它为特殊程序提供了一种重新加载和重新链接本机库的方法。

  • 参数: env: JNI 接口指针。

clazz:一个Java类对象。

  • 返回值: 成功返回“0”;失败时返回负值。

监控操作

监视器输入

jint MonitorEnter(JNIEnv *env, jobject obj);

输入与obj引用的基础 Java 对象关联的监视器。

输入与 obj 引用的对象关联的监视器。参考obj不能是NULL.

每个 Java 对象都有一个与之关联的监视器。如果当前线程已经拥有与obj关联的监视器 ,它会增加监视器中的计数器,指示该线程进入监视器的次数。如果与obj关联的监视器不属于任何线程,则当前线程成为监视器的所有者,将此监视器的条目计数设置为 1。如果另一个线程已经拥有与obj关联的监视器,则当前线程等待直到监视器被释放,然后再次尝试获得所有权。

通过MonitorEnterJNI 函数调用进入的监视器不能使用monitorexit Java 虚拟机指令或同步方法返回退出。MonitorEnterJNI 函数调用和 Java虚拟 monitorenter机指令可能会竞相进入与同一对象关联的监视器。

为避免死锁,通过 MonitorEnterJNI 函数调用进入的监视器必须使用MonitorExitJNI 调用退出,除非该 DetachCurrentThread调用用于隐式释放 JNI 监视器。

  • 参数: env: JNI 接口指针。

obj: 一个普通的 Java 对象或类对象。

  • 返回值: 成功返回“0”;失败时返回负值。

监视器退出

jint MonitorExit(JNIEnv *env, jobject obj);

当前线程必须是与 obj引用的底层 Java 对象关联的监视器的所有者。线程递减指示它进入此监视器的次数的计数器。如果计数器的值变为零,则当前线程释放监视器。

本机代码不得用于MonitorExit退出通过同步方法或 monitorenterJava 虚拟机指令输入的监视器。

  • 参数: env: JNI 接口指针。

obj: 一个普通的 Java 对象或类对象。

  • 返回值: 成功返回“0”;失败时返回负值。

  • 例外: IllegalMonitorStateException: 如果当前线程不拥有监视器。

创建ByteBuffer

jobject NewDirectByteBuffer(JNIEnv* env, void* 地址, jlong  capacity);

分配并返回一个直接的java.nio.ByteBuffer 引用从内存地址 地址开始并扩展容量字节的内存块。

调用此函数并将生成的字节缓冲区对象返回给 Java 级代码的本机代码应确保缓冲区指的是可读取的有效内存区域,如果合适,还可写入。尝试从 Java 代码访问无效内存位置将返回任意值、没有可见效果或引发未指定的异常。

  • 参数: env : JNIEnv接口指针

address:内存区域的起始地址(不能为NULL

capacity:内存区域的字节大小(必须为正)

  • 返回值: 返回对新实例化的 java.nio.ByteBuffer对象的本地引用。如果发生异常,或者此虚拟机不支持对直接缓冲区的 JNI 访问,则返回NULL 。

  • 例外: OutOfMemoryError : 如果分配 ByteBuffer对象失败

获取直接缓冲区地址

void* GetDirectBufferAddress(JNIEnv* env, jobject buf);

获取并返回给定直接java.nio.Buffer引用的内存区域的起始地址。

此函数允许本机代码通过缓冲区对象访问 Java 代码可访问的同一内存区域。

  • 参数: env : JNIEnv接口指针

buf:直接java.nio.Buffer对象(不能为NULL

  • 返回值: 返回缓冲区引用的内存区域的起始地址。如果内存区域未定义、给定对象不是直接 java.nio.Buffer或此虚拟机不支持对直接缓冲区的 JNI 访问,则返回NULL 。

获取直接缓冲容量

jlong GetDirectBufferCapacity(JNIEnv* env, jobject buf);

获取并返回给定直接java.nio.Buffer引用的内存区域的容量。容量是内存区域包含的元素数量。

  • 参数: env : JNIEnv接口指针

buf:直接java.nio.Buffer对象(不能为NULL

  • 返回值: 返回与缓冲区关联的内存区域的容量。如果给定对象不是直接 java.nio.Buffer,如果对象是未对齐的视图缓冲区并且处理器体系结构不支持未对齐访问,或者如果此虚拟机不支持对直接缓冲区的 JNI 访问,则返回-1 。

反射支持

如果程序员知道方法或字段的名称和类型,就可以使用 JNI 调用 Java 方法或访问 Java 字段。Java Core Reflection API 允许程序员在运行时内省 Java 类。JNI 提供了一组在 JNI 中使用的字段和方法 ID 到 Java Core Reflection API 中使用的字段和方法对象之间的转换函数。

将对象转换为方法ID

jmethodID FromReflectedMethod(JNIEnv *env, jobject method);

java.lang.reflect.Methodor java.lang.reflect.Constructor对象转换为方法 ID。

将字段转换为字段ID

jfieldID FromReflectedField(JNIEnv *env, jobject field);

java.lang.reflect.Field转换为字段 ID。

将方法ID转换为对象

jobject ToReflectedMethod(JNIEnv *env, jclass cls, jmethodID methodID, jboolean isStatic);

将派生自cls的方法 ID 转换为 java.lang.reflect.Methodor java.lang.reflect.Constructor对象。 如果方法 ID 引用静态字段,则isStatic必须设置为JNI_TRUE,否则设置为JNI_FALSE

OutOfMemoryError如果失败则抛出并返回 0。

ToReflectedField

jobject ToReflectedField(JNIEnv *env, jclass cls, jfieldID fieldID, jboolean isStatic);

将派生自cls的字段 ID 转换为 java.lang.reflect.Field对象。isStatic 必须设置为JNI_TRUEif fieldID引用静态字段,JNI_FALSE否则。

OutOfMemoryError如果失败则抛出并返回 0。

Java 虚拟机接口

获取JavaVM

jint GetJavaVM(JNIEnv *env, JavaVM **vm);

返回与当前线程关联的 Java VM 接口(在调用 API 中使用)。结果被放置在第二个参数vm指向的位置。

  • 参数: env: JNI 接口指针。

vm: 指向应该放置结果的位置的指针。

  • 返回值: 成功返回“0”;失败时返回负值。

调用API

调用 API 允许软件供应商将 Java VM 加载到任意本机应用程序中。供应商无需链接 Java VM 源代码即可交付支持 Java 的应用程序。

以下代码示例说明了如何在调用 API 中使用函数。在此示例中,C++ 代码创建了一个 Java VM 并调用了一个名为Main.test. 为了清楚起见,我们省略了错误检查。

 #include <jni.h>       /* 定义所有内容 */
    ...
    JavaVM *jvm;       /* 表示 Java 虚拟机 */
    JNIEnv *env;       /* 指向本地方法接口的指针 */
    JavaVMInitArgs vm_args; /* JDK/JRE 6 VM 初始化参数 */
    JavaVMOption* options = new JavaVMOption[1];
    options[0].optionString = "-Djava.class.path=/usr/lib/java";
    vm_args.version = JNI_VERSION_1_6;
    vm_args.nOptions = 1;
    vm_args.options = options;
    vm_args.ignoreUnrecognized = false;
    /*  加载并初始化一个Java VM,返回一个JNI接口
     *  环境中的指针 */
    JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);
    delete options;
    /* 使用 JNI 调用 Main.test 方法 */
    jclass cls = env->FindClass("Main");
    jmethodID mid = env->GetStaticMethodID(cls, "test", "(I)V");
    env->CallStaticVoidMethod(cls, mid, 100);
    /* We are done. */
    jvm->DestroyJavaVM();

此示例使用 API 中的三个函数。调用 API 允许本机应用程序使用 JNI 接口指针来访问 VM 功能。该设计类似于 Netscape 的 JRI 嵌入接口。

创建虚拟机

JNI_CreateJavaVM() 函数加载并初始化一个 Java VM,并返回一个指向 JNI 接口指针的指针。调用的线程JNI_CreateJavaVM()被认为是 主线程

附加到虚拟机

JNI 接口指针 ( JNIEnv) 仅在当前线程中有效。如果另一个线程需要访问 Java VM,它必须首先调用 AttachCurrentThread()以将自己附加到 VM 并获取 JNI 接口指针。一旦连接到 VM,本地线程就像在本地方法中运行的普通 Java 线程一样工作。本机线程保持连接到 VM,直到它调用DetachCurrentThread() 分离自身。

附加的线程应该有足够的堆栈空间来执行合理的工作量。每个线程的堆栈空间分配是特定于操作系统的。例如,使用 pthreads,堆栈大小可以在pthread_attr_t参数中指定pthread_create

从虚拟机分离

附加到 VM 的本机线程必须调用 DetachCurrentThread()以在退出之前分离自身。如果调用堆栈上有 Java 方法,则线程不能自行分离。

卸载虚拟机

JNI_DestroyJavaVM()函数卸载 Java VM。从 JDK/JRE 1.1 开始,只有主线程可以通过调用DestroyJavaVM. 从 JDK/JRE 1.2 开始,该限制被删除,任何线程都可以调用 DestroyJavaVM来卸载 VM。

VM 会一直等待,直到 当前线程是唯一的非守护程序用户线程,然后才会实际卸载。用户线程包括 Java 线程和附加的本机线程。存在此限制是因为 Java 线程或附加的本机线程可能持有系统资源,例如锁、窗口等。VM无法自动释放这些资源。通过在 卸载 VM 时将当前线程限制为唯一运行的线程,释放任意线程持有的系统资源的负担就落在了程序员身上。

库和版本管理

从 JDK/JRE 1.1 开始,一旦加载了本机库,它就可以从所有类加载器中看到。因此,不同类加载器中的两个类可能会链接到同一个本地方法。这会导致两个问题:

  • 一个类可能会错误地链接到由不同类加载器中的同名类加载的本机库。
  • 本机方法可以轻松混合来自不同类加载器的类。这打破了类加载器提供的名称空间分离,并导致类型安全问题。

从 JDK/JRE 1.2 开始,每个类加载器都管理自己的一组本地库。不能将同一个 JNI 本机库加载到多个类加载器中。 这样做会导致 UnsatisfiedLinkError被抛出。例如, 在用于将本机库加载到两个类加载器时System.loadLibrary抛出一个 。UnsatisfiedLinkError新方法的好处是:

  • 基于类加载器的名称空间分离保留在本机库中。本机库不能轻易地混合来自不同类加载器的类。
  • 此外,本地库可以在其相应的类加载器被垃圾回收时被卸载。

为了便于版本控制和资源管理,JDK/JRE 1.2 的 JNI 库可以选择导出以下两个函数:

加载本机库

jint JNI_OnLoad(JavaVM *vm, void *reserved);

加载本机库时VM 调用JNI_OnLoad(例如,通过System.loadLibrary)。 JNI_OnLoad必须返回本机库所需的 JNI 版本。

为了使用任何新的 JNI 函数,本机库必须导出一个JNI_OnLoad返回 JNI_VERSION_1_2. 如果原生库不导出JNI_OnLoad函数,VM 会假定该库只需要 JNI 版本JNI_VERSION_1_1。如果 VM 无法识别返回的版本号 JNI_OnLoad,则无法加载本机库。

卸载本机库

void JNI_OnUnload(JavaVM *vm, void *reserved);

JNI_OnUnload当包含本机库的类加载器被垃圾收集时, VM 调用。此函数可用于执行清理操作。因为这个函数是在未知的上下文中调用的(比如从一个终结器),所以程序员在使用 Java VM 服务时应该保持谨慎,并且避免任意的 Java 回调。

请注意,JNI_OnLoadJNI_OnUnload 是 JNI 库可选提供的两个函数,而不是从 VM 导出的。

调用 API 函数

JavaVM类型是指向调用 API 函数表的指针。以下代码示例显示了此函数表。

typedef const struct JNIInvokeInterface *JavaVM;

const struct JNIInvokeInterface ... = {
    NULL,
    NULL,
    NULL,

    DestroyJavaVM,
    AttachCurrentThread,
    DetachCurrentThread,

    GetEnv,

    AttachCurrentThreadAsDaemon
};

请注意,三个调用 API 函数 、 JNI_GetDefaultJavaVMInitArgs()和 JNI_GetCreatedJavaVMs()不是JNI_CreateJavaVM()JavaVM 函数表的一部分。这些功能可以在没有预先存在的 JavaVM结构的情况下使用。

获得虚拟机的默认配置

jint JNI_GetDefaultJavaVMInitArgs(void *vm_args);

返回 Java VM 的默认配置。在调用此函数之前,本机代码必须将 vm_args->version 字段设置为它期望 VM 支持的 JNI 版本。此函数返回后,vm_args->version 将设置为 VM 支持的实际 JNI 版本。

  • 参数: vm_args: 指向 JavaVMInitArgs填充默认参数的结构的指针。

  • 返回值: JNI_OK如果支持请求的版本,则返回;如果请求的版本不受支持,则返回 JNI 错误代码(负数)。

获得所有已创建的虚拟机

jint JNI_GetCreatedJavaVMs(JavaVM **vmBuf, jsize bufLen, jsize *nVMs);

返回所有已创建的 Java VM。指向 VM 的指针按照创建顺序写入缓冲区 vmBuf。最多会写入 bufLen 个条目。在 *nVMs 中返回创建的 VM 总数。

从 JDK/JRE 1.2 开始,不支持在单个进程中创建多个 VM。

  • 参数: vmBuf: 指向将放置 VM 结构的缓冲区的指针。

bufLen: 缓冲区的长度。

nVMs: 指向整数的指针。

  • 返回值: 成功的回报JNI_OK;失败时返回合适的 JNI 错误代码(负数)。

加载并初始化虚拟机

jint JNI_CreateJavaVM(JavaVM **p_vm, void **p_env, void *vm_args);

加载并初始化 Java VM。当前线程成为主线程。将env参数设置为主线程的 JNI 接口指针。

从 JDK/JRE 1.2 开始,不支持在单个进程中创建多个 VM。

to 的第二个参数JNI_CreateJavaVM始终是指向 的指针JNIEnv *,而第三个参数是指向 JavaVMInitArgs使用选项字符串编码任意 VM 启动选项的结构的指针:

typedef struct JavaVMInitArgs {
    jint version;

    jint nOptions;
    JavaVMOption *options;
    jboolean ignoreUnrecognized;
} JavaVMInitArgs;

version字段必须至少设置为 JNI_VERSION_1_2。该options字段是以下类型的数组:

typedef struct JavaVMOption {
    char *optionString;  /* the option as a string in the default platform encoding */
    void *extraInfo;
} JavaVMOption;

数组的大小由 中的 nOptions 字段表示 JavaVMInitArgs。如果ignoreUnrecognized是 JNI_TRUE,则忽略所有以“ ”或“ ”JNI_CreateJavaVM开头的无法识别的选项字符串。如果是 ,则在遇到任何无法识别的选项字符串时立即返回 。所有 Java VM 都必须识别以下一组标准选项:-X``_``ignoreUnrecognized``JNI_FALSE``JNI_CreateJavaVM``JNI_ERR

选项字符串意义
-D<name>=<value>设置系统属性
`-verbose[:classgcjni]`启用详细输出。选项后面可以跟以逗号分隔的名称列表,指示 VM 将打印哪种消息。例如,“ -verbose:gc,class”指示 VM 打印 GC 和类加载相关消息。标准名称包括:gcclass和 jni。所有非标准(特定于 VM)的名称必须以“”开头X
vfprintfextraInfo是指向 vfprintf钩子的指针。
exitextraInfo是指向exit 钩子的指针。
abortextraInfo是指向abort 钩子的指针。

外,每个 VM 实现都可以支持自己的一组非标准选项字符串。非标准选项名称必须以 " -X" 或下划线 (" _") 开头。例如,JDK/JRE 支持-Xms和 -Xmx选项允许程序员指定初始和最大堆大小。以“”开头的选项-X可从“ java”命令行访问。

以下是在 JDK/JRE 中创建 Java VM 的示例代码:

JavaVMInitArgs vm_args;
JavaVMOption options[4];

options[0].optionString = "-Djava.compiler=NONE";           /* disable JIT */
options[1].optionString = "-Djava.class.path=c:\myclasses"; /* user classes */
options[2].optionString = "-Djava.library.path=c:\mylibs";  /* set native library path */
options[3].optionString = "-verbose:jni";                   /* print JNI-related messages */

vm_args.version = JNI_VERSION_1_2;
vm_args.options = options;
vm_args.nOptions = 4;
vm_args.ignoreUnrecognized = TRUE;

/* Note that in the JDK/JRE, there is no longer any need to call
 * JNI_GetDefaultJavaVMInitArgs.
 */
res = JNI_CreateJavaVM(&vm, (void **)&env, &vm_args);
if (res < 0) ...
  • 参数: p_vm: 指向将放置生成的 VM 结构的位置的指针。

p_env: 指向主线程的 JNI 接口指针将被放置的位置的指针。

vm_args:Java VM 初始化参数。

  • 返回值: 成功的回报JNI_OK;失败时返回合适的 JNI 错误代码(负数)。

销毁JavaVM

jint DestroyJavaVM(JavaVM *vm);

卸载 Java VM 并回收其资源。

JDK/JRE 1.1 中的支持DestroyJavaVM不完整。从 JDK/JRE 1.1 开始,只有主线程可以调用DestroyJavaVM. 从 JDK/JRE 1.2 开始,任何线程,无论是否附加,都可以调用这个函数。如果当前线程已附加,则 VM 会一直等待,直到当前线程是唯一的非守护程序用户级 Java 线程。如果当前线程没有附加,VM 附加当前线程,然后等待,直到当前线程是唯一的非守护用户级线程。但是,JDK/JRE 仍然不支持 VM 卸载。

  • 参数: vm: 将被销毁的 Java VM。

  • 返回值: 成功的返回JNI_OK;失败时返回合适的 JNI 错误代码(负数)。

附加当前线程

jint AttachCurrentThread(JavaVM *vm, void **p_env, void *thr_args);

将当前线程附加到 Java VM。JNIEnv 在参数中返回一个 JNI 接口指针。

尝试附加已附加的线程是无操作的。

本机线程不能同时连接到两个 Java VM。

当一个线程连接到虚拟机时,上下文类加载器就是引导加载器。

  • 参数: vm:当前线程将附加到的 VM。

p_env:指向当前线程的JNI接口指针将要放置的位置的指针。

thr_args: 可以是 NULL 或指向JavaVMAttachArgs 结构的指针以指定附加信息:

从 JDK/JRE 1.1 开始,第二个参数 to AttachCurrentThread始终是指向 JNIEnv. 的第三个参数 AttachCurrentThread是保留的,应该设置为 NULL

从 JDK/JRE 1.2 开始,您将NULL作为 1.1 行为的第三个参数传递,或者传递指向以下结构的指针以指定附加信息:

typedef struct JavaVMAttachArgs {
    jint version;  /* must be at least JNI_VERSION_1_2 */
    char *name;    /* the name of the thread as a modified UTF-8 string, or NULL */
    jobject group; /* global ref of a ThreadGroup object, or NULL */
} JavaVMAttachArgs
  • 返回值: 成功的返回JNI_OK;失败时返回合适的 JNI 错误代码(负数)。

附加当前线程作为守护进程

jint AttachCurrentThreadAsDaemon(JavaVM* vm, void** penv, void* args);

与AttachCurrentThread语义相同,但新创建的java.lang.Thread实例是一个 daemon

如果线程已经通过 AttachCurrentThread或 AttachCurrentThreadAsDaemon附加,则此例程只需将penv指向的值设置为当前线程的JNIEnv。在这种情况下,AttachCurrentThread和该例程都不会对线程的守护进程状态 产生任何影响。

  • 参数: vm:当前线程将附加到的虚拟机实例。

penv:指向当前线程的 JNIEnv接口指针将被放置的位置的指针。

args:指向JavaVMAttachArgs 结构的指针。

  • 返回值: 成功的返回JNI_OK;失败时返回合适的 JNI 错误代码(负数)。

分离当前线程

jint DetachCurrentThread(JavaVM *vm);

从 Java VM 中分离当前线程。此线程持有的所有 Java 监视器都被释放。通知所有等待该线程终止的 Java 线程。

从 JDK/JRE 1.2 开始,主线程可以与 VM 分离。

  • 参数: vm:将从中检索接口的虚拟机实例。
    env: 指向当前线程的 JNI 接口指针将被放置的位置的指针。
    version: 请求的 JNI 版本。\

  • 返回值: 如果当前线程未附加到 VM,则设置 *envNULL并返回 JNI_EDETACHED。如果不支持指定的版本,则设置*envNULL,并返回 JNI_EVERSION。否则,设置*env为适当的接口,并返回JNI_OK