簡介
- 通常不必關心內部對象釋放內存問題,因為已經交由 Java虛擬機 負責。
-
JNI 支持3中不透明引用: 局部引用 、 全局引用 、 弱全局引用 。
局部引用
簡介
-
局部引用 可以由一系列 JNI函數 獲取。
-
局部引用 會在原生函數返回時被自動釋放。
- 只能在創建了 局部引用 的線程使用此 局部引用 ,不該使用全局變量存儲 局部引用 指望別的線程能使用此引用。
- 不要使用 靜態存儲生命周期變量 (即全局變量,局部靜態變量)存儲 局部引用 ,然后指望下次使用此靜態變量時, 局部引用 仍然有效。
-
局部引用 可以通過
DeleteLocalRef()
手動釋放。
手動釋放局部引用
- 通常情況下不必手動釋放 局部引用 ,但是在以下情況則需要手動釋放:
- 創建了大量的 局部引用 ,如數組元素等,可以每用完一個釋放一個。
- 這個 局部引用 是在 效用函數 中創建的。
- 這個 局部引用 指向一個巨大的對象。
- 原生函數永遠都不會返回,比如一個永久循環。
管理局部引用
函數解說
-
EnsureLocalCapacity()
:用于確保有足夠的空間給某個數量的 局部引用 。
-
PushLocalFrame() / PopLocalFrame()
:
- 開始一段區域,在此區域創建的 局部引用 都會被記錄,在隨后調用
PopLocalFrame()
時會釋放這段區域創建的所有 局部引用 。
-
PushLocalFrame() / PopLocalFrame()
十分有效率,可以在進入 效用函數 時調用PushLocalFrame()
,在return
的地方調用PopLocalFrame()
。若有多個return
的時候,每個return
處都需要調用PopLocalFrame()
。
-
PopLocalFrame()
的第二個參數十分有用,在碰見需要釋放所有 局部引用 除了其中一個保留并返回時,可以將此引用傳遞PopLocalFrame()
第二個參數。
-
NewLocalRef()
:用于在 效用函數 中返回一個 局部引用 。
EnsureLocalCapacity()
示例
// 開始獲取局部引用前先確保有足夠的空間
if (((* env)->EnsureLocalCapacity(env, len)) < 0) {
// 內存不足
}
for (int i = 0; i < len; i++) {
jstring jstr = (* env)->GetObjectArrayElement(env, arr, i);
}
PushLocalFrame() / PopLocalFrame()
示例
jobject f(JNIEnv * env, ...) {
jobject result = NULL;
// 開始一段區域,并保證有足夠的空間
if ((*env)->PushLocalFrame(env, 10) < 0) {
return NULL;
}
// ...
result = ...;
if (...) {
// 結束這段區域,區域中獲取的局部引用全部釋放,result通過傳入傳出得到了保留
result = (* env)->PopLocalFrame(env, result);
return result;
}
// 保證所有的return前都調用了PopLocalFrame
result = (* env)->PopLocalFrame(env, result);
return result;
}
NewLocalRef()
示例
jstring MyNewString(JNIEnv * env, jchar * chars, jint len) {
static jstring result;
if (wstrncmp("CommonString", chars, len) == 0) {
static jstring cachedString = NULL;
if (cachedString == NULL) {
jstring cachedStringLocal = ...;
cachedString = (* env)->NewGlobalRef(env, cachedStringLocal);
}
// 將全局引用cachedString轉換為局部引用后返回
return (* env)->NewLocalRef(env, cachedString);
}
return result;
}
全局引用
- 和 局部引用 不一樣, 全局引用 只需要一個 JNI函數 便能獲取,即
NewGlobalRef()
。
- 可以使用 全局引用 在多個函數調用間或多個線程間共享。
-
全局引用 不會被自動釋放,需要手動使用
DeleteGlobalRef()
釋放。
jstring MyNewString(JNIEnv * env, jchar * chars, jint len) {
// 可以使用局部靜態變量來存儲全局引用
static jclass stringClass = NULL;
if (stringClass == NULL) {
jclass localRefCls = (* env)->FindClass(env, "java/lang/String");
if (localRefCls == NULL) {
return NULL;
}
// 通過局部引用來獲取全局引用
stringClass = (* env)->NewGlobalRef(env, localRefCls);
// 先釋放局部引用,再檢查是否成功獲取全局引用
// 因為無論是否成功,局部引用都不再有效
(* env)->DeleteLocalRef(env, localRefCls);
if (stringClass == NULL) {
return NULL;
}
}
// ...
}
弱全局引用
-
弱全局引用 使用
NewWeakGlobalRef()
來獲取。
-
弱全局引用 和 全局引用 一樣,可以在多個函數調用間或多個線程間共享。
-
弱全局引用 可能會被垃圾回收器回收。
-
弱全局引用 雖然可能會被自動釋放,但仍然需要使用
DeleteWeakGlobalRef()
來手動釋放。
- 像
java.lang.String
這樣的系統類永遠不會被垃圾回收器回收。
JNIEXPORT void JNICALL Java_mypkg_MyCls_f(JNIEnv * env, jobject self) {
static jclass myCls2 = NULL;
if (myCls2 == NULL) {
jclass myCls2Local = (* env)->FindClass(env, "mypkg/MyCls2");
if (myCls2Local == NULL) {
return;
}
// 使用局部引用獲取弱全局引用
myCls2 = (* env)->NewWeakGlobalRef(env, myCls2Local);
if (myCls2 == NULL) {
return;
}
}
// ...
}
比較引用
- 可以使用
IsSameObject()
來檢查兩個引用(局部、全局或弱全局)是否指向同一個對象。如果返回JNI_TRUE
,則兩個引用指向同一個對象。如果返回JNI_FALSE
,則相反。
- 若引用的值為
NULL
,則此引用指向 Java 中的null
對象。
- 對于 局部引用 和 全局引用 可以通過
(* env)->IsSameObject(env, obj, NULL)
來判斷引用是否指向null
對象。若返回JNI_TRUE
則指向null
對象。
- 對于 弱全局引用 可以通過
(* env)->IsSameObject(env, wobj, NULL)
來檢查到更多。若返回JNI_TRUE
,則表明引用要么指向null
對象,要么被垃圾回收器回收了。無論哪一種,引用都不能再使用了。
管理引用的規則
-
JNI 代碼中有三種類型的函數:
-
JNI函數 : JNI 的 API 函數。
-
原生函數 :實現 Java 代碼中 native方法聲明 的 C 函數。
-
效用函數 : C 工具函數。
- 對于 原生函數 ,應該注意:
- 避免大量創建 局部引用 。
- 避免不返回的 原生函數 創建了不必要的 局部引用 。
- 對于 效用函數 ,應該注意:
- 盡量避免內存泄漏。
- 如果此 效用函數 返回 原始類型 ,應該釋放所有的 局部引用 。
- 如果此 效用函數 返回 引用類型 ,應該釋放除了要返回的 引用類型 以外所有的 局部引用 。
- 不應該偶爾返回 局部引用 ,偶爾返回 全局引用 ,因為調用此 效用函數 的調用者需要知道返回的 引用類型 ,以便管理這個引用。
- 可以使用
NewLocalRef()
來保證 效用函數 返回的 引用類型 總是 局部引用 。
while (JNI_TRUE) {
// GetInfoString是一個效用函數,infoString是由此效用函數創建的
jstring infoString = GetInfoString(info);
// 使用infoString
// 使用完畢后,在此處需要根據infoString的引用類型(局部引用、全局引用或弱全局引用)來釋放infoString。
}
最后編輯于 :
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。