JNI 对象引用概述

JNI 规范中未详细说明有关 GC 如何查找 JNI 对象引用的实现详细信息。 相反,JNI 指定了可靠且可预测的必需行为。

局部和全局引用

局部引用限于其创建的堆栈帧和线程,并且在其创建的堆栈帧返回时会自动删除。 全局引用允许本机代码将局部引用提升为与 JVM 相连的任何线程中的本机代码可以使用的格式。

全局引用和内存泄漏

全局引用不会自动删除,因此程序员必须处理内存管理。 每个全局引用都会为引用目标建立根,并且使用户可以访问整个子树。 因此,必须释放创建的每个全局引用以防止发生内存泄漏。

全局引用中的泄漏最终会导致发生内存不足异常。 这些错误可能难于解决,尤其是在您不执行 JNI 异常处理时。 请参阅 处理异常

为了提供 JNI 全局引用功能并提供某些对引用目标的自动垃圾回收功能,JNI 提供了两个函数:
  • NewWeakGlobalRef
  • DeleteWeakGlobalRef
这些函数为 JNI 提供弱引用访问。

局部引用和内存泄漏

局部引用范围之外的自动垃圾回收可在大多数情况下防止发生内存泄漏。 当本机线程返回到 Java (本机方法) 或从 JVM (调用 API) 拆离时,将发生此自动垃圾回收。 如果不执行自动垃圾回收,那么可能会出现局部引用内存泄漏。 如果本机方法未返回至 JVM,或者如果使用 Invocation API 的程序未断开与 JVM 的连接,那么可能会发生内存泄漏。

请考虑以下示例中的代码,其中本机代码将在循环中创建新的局部引用:

while ( <condition> )
{
   jobject myObj = (*env)->NewObject( env, clz, mid, NULL );

   if ( NULL != myObj )
   {
      /* we know myObj is a valid local ref, so use it */
      jclass myClazz = (*env)->GetObjectClass(env, myObj);

      /* uses of myObj and myClazz, etc. but no new local refs */

      /* Without the following calls, we would leak */
      (*env)->DeleteLocalRef( env, myObj ); 
      (*env)->DeleteLocalRef( env, myClazz );
   }

} /* end while */

虽然新的本地引用会覆盖循环内的 myObjmyClazz 变量,但每个本地引用都保留在根集中。 必须使用 DeleteLocalRef 调用来显式移除这些引用。 如果没有 DeleteLocalRef 调用,那么将泄漏本地引用,直到线程返回到 Java 或从 JVM 拆离为止。

JNI 弱全局引用

弱全局引用是特殊类型的全局引用。 可在任何线程中使用,并且可在本机函数调用之间使用,但不能用作 GC 根。 如果弱全局引用所引用的对象在其他位置无强引用,那么 GC 可随时处理该对象。

使用弱全局引用时必须谨慎。 如果对弱全局引用所引用的对象执行垃圾回收,那么该引用将变为空引用。 空引用仅在与 JNI 函数的子集一起使用时才安全。 要测试是否已回收弱全局引用,请使用 IsSameObject JNI 函数将弱全局引用与空值进行比较。

即使您已测试过弱全局引用不是空引用,使用该引用调用大多数 JNI 函数也不安全,这是因为弱全局引用在经过测试后,甚至在 JNI 调用函数期间都可能变为空引用。 而弱全局引用在使用前应始终被提升为强引用。 您可以使用 NewLocalRefNewGlobalRef JNI 函数来提升弱全局引用。

弱全局引用占用内存,并且在不再需要时必须使用 DeleteWeakGlobalRef JNI 函数来释放。 释放弱全局引用失败会导致内存缓慢泄漏,最终导致发生内存不足异常。

有关使用 JNI 全局弱引用的信息和警告,请参阅 JNI 规范。

JNI 引用管理

提供了一组独立于平台的规则来管理 JNI 引用

这些规则包括:

  1. JNI 引用仅在与 JVM 相连的线程中才有效。
  2. 必须在本机代码中获取作为以下项的有效 JNI 局部引用:
    1. 本机代码的参数
    2. 调用 JNI 函数的返回值
  3. 必须通过调用 NewGlobalRefNewWeakGlobalRef,从另一个有效的 JNI 引用(全局或局部)获取有效的 JNI 全局引用。
  4. 空值引用始终是有效的,并且可用于代替任何 JNI 引用(全局或局部)。
  5. JNI 局部引用仅在创建它们的线程中才有效,并且仅在它们创建的帧仍然位于堆栈上时才有效。
注:
  1. 使用空值覆盖本机存储中的局部或全局引用不会从根集中移除该引用。 使用适当的 Delete*Ref JNI 函数以从根集中移除引用。
  2. 如果存在异常暂挂,那么多个 JNI 函数(例如,FindClassNewObject)将返回空值。 将返回的值与这些调用的空值进行比较在语义上等同于调用 JNI ExceptionCheck 函数。 请参阅 JNI 规范以获取更多详细信息。
  3. JNI 局部引用创建的帧返回后,无论在任何情况下,都不得使用该引用。 将 JNI 局部引用存储到任何进程静态存储器中十分危险。