JNI 物件參照概觀

GC 如何找到 JNI 物件參照的實作詳細資料未在 JNI 規格中詳細說明。 相反地, JNI 會指定可靠且可預測的必要行為。

區域和廣域參照

本端參照以其建立中的堆疊框和執行緒為範圍,並在其建立中的堆疊框傳回時自動刪除。 廣域參照可讓原生程式碼將本端參照提升為可由附加至 JVM 之任何執行緒中的原生程式碼使用的表單。

廣域參照及記憶體洩漏

廣域參照不會自動刪除,因此程式設計師必須處理記憶體管理。 每個廣域參照都會建立參照的根,並使其整個子樹狀結構都可呼叫到。 因此,必須釋放所建立的每個廣域參照,以防止記憶體洩漏。

廣域參照中的洩漏最終會導致記憶體不足異常狀況。 這些錯誤可能難以解決,特別是如果您未執行 JNI 異常狀況處理。 請參閱 處理異常狀況

為了提供 JNI 廣域參照功能,以及提供一些參照的自動記憶體回收, JNI 提供兩個功能:
  • NewWeakGlobalRef
  • DeleteWeakGlobalRef
這些函數提供對弱參照的 JNI 存取權。

本端參照及記憶體洩漏

已不在範圍內的本端參照自動記憶體回收,可防止在大部分狀況下發生記憶體洩漏。 當原生執行緒回到 Java (原生方法) 或從 JVM 分離 (呼叫 API) 時,會發生這個自動記憶體回收。 如果未發生自動記憶體回收,則可能發生本端參照記憶體洩漏。 如果原生方法未回到 JVM ,或使用「呼叫 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 本端參照儲存在任何程序靜態儲存體中是很危險的。