JNI全域性引用與JFrame.dispose()方法
問題描述
用 jProfiler 分析 Java swing 程式中的記憶體洩漏問題時, 我發現記憶體中 JFrame 例項的數量一直在增加。
各個 frame 被開啟(opened),然後被關閉(closed)。
通過 jProfiler, 並檢視GC Root時, 只找到一項: ‘JNI Global reference’。
這是什麼意思? 為什麼他 hang 住了所有的 frame 例項?
回答1
請檢視《維基百科》中關於 Java本地介面 的介紹, 本質上它允許 Java程式 和系統庫之間進行通訊。
JNI全域性引用很容易造成記憶體洩漏, 因為它們不能被自動垃圾收集所清理, 程式設計師必須顯式地釋放它們. 如果你沒有編寫任何JNI程式碼, 那麼狠可能是使用的庫中存在記憶體洩漏。
修正: 請參考關於 local vs. global references 的更多資訊. 其中介紹了為什麼要使用全域性引用(以及如何進行釋放)。
回答2
JNI全域性引用(JNI global reference), 是從 “native” 程式碼指向堆記憶體中Java物件的引用. 其存在的目的是阻止垃圾收集器, 不要誤將 native 程式碼中仍在使用的物件給回收了, 假如這些Java物件沒有Java程式碼引用到他們的話。
一個 JFrame 例項就是一個視窗(java.awt.Window
), 並關聯到一個本地的 Window
物件。如果某個特定 JFrame 例項的任務已經結束,那麼就應該呼叫 dispose()
方法來執行清理。
我不確定原生程式碼是否建立了全域性引用來指向 JFrame, 但應該是這樣沒錯。如果確實建立了全域性引用, 那就會阻止 JFrame 物件被GC回收. 如果程式中建立了很多 Window 物件(或其子類物件), 而又沒有呼叫 dispose(), 則他們永遠都不會被GC回收掉, 這就造成了隱形的記憶體洩露。
區域性引用和全域性引用簡介
JNI為所有傳遞給 native 方法的物件型引數建立了引用, 同時也對所有從JNI函式返回的物件建立引用。
這些引用會阻止Java物件被GC清理。為了確保Java物件最終被釋放, JNI預設建立的是區域性引用(local references). 當本機方法返回時, 其建立的區域性引用就會失效。當然, native 方法不應該將區域性引用存到其他地方, 妄圖在後續呼叫中進行重用。
例如,以下程式(FieldAccess.c
中的一種變體native方法) 錯誤地將Java類的 ID field 快取起來, 期待不必每次都通過欄位名稱和簽名去搜尋 ID field ,:
/* !!! 這是一段問題程式碼 */
static jclass cls = 0;
static jfieldID fid;
JNIEXPORT void JNICALL
Java_FieldAccess_accessFields(JNIEnv *env, jobject obj)
{
...
if (cls == 0) {
cls = (*env)->GetObjectClass(env, obj);
if (cls == 0)
... /* error */
fid = (*env)->GetStaticFieldID(env, cls, "si", "I");
}
... /* access the field using cls and fid */
}
這個程式是錯誤的,因為從 GetObjectClass
返回的區域性引用只在 native 方法返回前才有效。第二次進入 Java_FieldAccess_accessField
方法時, 將會引用到一個無效的 local reference。 這會引起錯誤的結果甚至導致 JVM 崩潰。
要解決這種問題, 可以建立全域性引用(global reference)。全域性引用將一直有效,直到顯式釋放:
/* 本段程式碼是OK的 */
static jclass cls = 0;
static jfieldID fid;
JNIEXPORT void JNICALL
Java_FieldAccess_accessFields(JNIEnv *env, jobject obj)
{
...
if (cls == 0) {
jclass cls1 = (*env)->GetObjectClass(env, obj);
if (cls1 == 0)
... /* error */
cls = (*env)->NewGlobalRef(env, cls1);
if (cls == 0)
... /* error */
fid = (*env)->GetStaticFieldID(env, cls, "si", "I");
}
... /* access the field using cls and fid */
}
全域性引用一直存在,直到Java類被解除安裝之後。 因此保證了在下次用到Java類的ID欄位時其一直有效。 native 程式碼不再使用全域性引用時必須呼叫 DeleteGlobalRefs
函式; 否則,對應的Java物件(如 cls引用的Java類)永遠都不會被解除安裝。
在大多數情況下, native 程式設計師應該依靠VM來釋放所有的區域性引用. 在某些特殊情況下,native 程式碼也可以呼叫 DeleteLocalRef
函式來顯式地刪除區域性引用。這些情況包括:
引用了某個龐大的Java物件, 不想等當前 native 方法返回時才讓Java物件被GC回收。例如,在下面的程式中, GC 可以釋放lref
所引用的Java物件, 而此時 lengthyComputation 方法還在執行中:
lref = ... /* a large Java object */
... /* last use of lref */
(*env)->DeleteLocalRef(env, lref);
lengthyComputation(); /* may take some time */
return; /* all local refs will now be freed */
}
也可能會在 native 方法中建立大量的區域性引用。這很可能會導致 JNI local reference table 溢位. 這時候刪除那些不用的區域性引用是挺有必要的。 例如下面的程式中, native 程式碼遍歷一個由 java字串組成的大陣列. 每次迭代之後,都可以釋放字串元素的區域性引用:
for(i = 0; i < len; i++) {
jstring jstr = (*env)->GetObjectArrayElement(env, arr, i);
... /* processes jstr */
(*env)->DeleteLocalRef(env, jstr); /* no longer needs jstr */
}
參考:
翻譯人員: 鐵錨 http://blog.csdn.net/renfufei
翻譯時間: 2017年01月28日
相關文章
- JNI多執行緒與全域性引用執行緒
- less 全域性變數使用 引用變數
- php傳引用和全域性變數PHP變數
- vue定義全域性變數和全域性方法Vue變數
- Android NDK開發之旅11 JNI JNI資料型別與方法屬性訪問Android資料型別
- 全域性元件實現遞迴樹,避免迴圈引用元件遞迴
- webpack+vue+sass專案中,sass提取全域性引用WebVue
- JNI開發流程與引用資料型別的處理資料型別
- 區域性方法$(“html”).load()和全域性方法$.get()、$.post()HTML
- 兩種方式配置vue全域性方法Vue
- QT 全域性變數使用方法QT變數
- [Vue] 如何定義全域性的方法?Vue
- 全域性變數與區域性變數變數
- win10全域性字型更換方法_win10怎樣更改全域性字型Win10
- Java區域性變數與全域性變數Java變數
- 【c】全域性變數與區域性變數變數
- node 全域性物件和全域性變數物件變數
- lambda方法引用獲取欄位屬性
- python全域性變數與區域性變數Python變數
- Tomcat全域性/區域性https訪問配置方法TomcatHTTP
- Python與全域性直譯器鎖Python
- Vue+ElementUI建立全域性元件方法及呼叫VueUI元件
- 【GiraKoo】Java Native Interface(JNI)的空間(引用)管理Java
- 靜態全域性變數和全域性變數變數
- 區域性代理ip與全域性代理ip怎麼用?
- iOS全域性變數與屬性的記憶體管理iOS變數記憶體
- JNI訪問Java方法Java
- jni回撥java方法Java
- angular中的全域性方法和指令介紹1Angular
- 框架(frameset),全域性屬性框架
- 方法引用
- Python全域性變數與區域性變數詳解Python變數
- 詳解python 區域性變數與全域性變數Python變數
- 全域性變數變數
- vue全域性APIVueAPI
- Js全域性物件JS物件
- CMake 屬性之全域性屬性
- vue2.0全域性路由守衛(全域性控制登入)Vue路由