global reference和local reference

JNI提供了一些實例和數(shù)組類型(jobject诫肠、jclass捍岳、jstring紧帕、jarray等)作為不透明的引用供本地代碼使用。本地代碼永遠不會直接操作引用指向的VM內部的數(shù)據內容愉烙。要進行這些操作,必須通過使用JNI操作一個不引用來間接操作數(shù)據內容解取。因為只操作引用步责,你不必擔心特定JVM中對象的存儲方式等信息。這樣的話,你有必要了解一下JNI中的幾種不同的引用:
1蔓肯、 JNI支持三種引用:局部引用遂鹊、全局引用、弱全局引用(下文簡稱“弱引用”)秉扑。
2调限、 局部引用和全局引用有不同的生命周期。當本地方法返回時秦躯,局部引用會被自動釋放裆装。而全局引用和弱引用必須手動釋放。
3哨免、 局部引用或者全局引用會阻止GC回收它們所引用的對象琢唾,而弱引用則不會。
4身辨、 不是所有的引用可以被用在所有的場合芍碧。例如,一個本地方法創(chuàng)建一個局部引用并返回后泌豆,再對這個局部引用進行訪問是非法的踪危。
本章中,我們會詳細地討論這些問題贞远。合理地管理JNI引用是寫出高質量的代碼的基礎。
5.1 局部引用和全局引用
什么是全局引用和局部引用俱病?它們有什么不同?我們下面使用一些例子來說明途凫。
5.1.1 局部引用
大多數(shù)JNI函數(shù)會創(chuàng)建局部引用溢吻。例如,NewObject創(chuàng)建一個新的對象實例并返回一個對這個對象的局部引用犀盟。
局部引用只有在創(chuàng)建它的本地方法返回前有效硼砰。本地方法返回后,局部引用會被自動釋放恶阴。
你不能在本地方法中把局部引用存儲在靜態(tài)變量中緩存起來供下一次調用時使用豹障。下面的例子是MyNewString函數(shù)的一個修改版本,這里面使用局部引用的方法是錯誤的:
/* This code is illegal */
jstring
MyNewString(JNIEnv *env, jchar *chars, jint len)
{
static jclass stringClass = NULL;
jmethodID cid;
jcharArray elemArr;
jstring result;

 if (stringClass == NULL) {
     stringClass = (*env)->FindClass(env,
                                     "java/lang/String");
     if (stringClass == NULL) {
         return NULL; /* exception thrown */
     }
 }
 /* It is wrong to use the cached stringClass here,
    because it may be invalid. */
 cid = (*env)->GetMethodID(env, stringClass,
                           "<init>", "([C)V");
 ...
 elemArr = (*env)->NewCharArray(env, len);
 ...
 result = (*env)->NewObject(env, stringClass, cid, elemArr);
 (*env)->DeleteLocalRef(env, elemArr);
 return result;

}
上面代碼中昵仅,我們省略了和我們的討論無關的代碼累魔。因為FindClass返回一個對java.lang.String對象的局部引用垦写,上面的代碼中緩存stringClassr做法是錯誤的。假設一個本地方法C.f調用了MyNewString:
JNIEXPORT jstring JNICALL
Java_C_f(JNIEnv *env, jobject this)
{
char c_str = ...;
...
return MyNewString(c_str);
}
C.f方法返回后梯投,VM釋放了在這個方法執(zhí)行期間創(chuàng)建的所有局部引用分蓖,也包含對String類的引用stringClass。當再次調用MyNewString時么鹤,會試圖訪問一個無效的局部引用午磁,從而導致非法的內存訪問甚至系統(tǒng)崩潰毡们。
釋放一個局部引用有兩種方式昧辽,一個是本地方法執(zhí)行完畢后VM自動釋放登颓,另外一個是程序員通過DeleteLocalRef手動釋放。
既然VM會自動釋放局部引用咕痛,為什么還需要手動釋放呢喇嘱?因為局部引用會阻止它所引用的對象被GC回收。
局部引用只在創(chuàng)建它們的線程中有效者铜,跨線程使用是被禁止的。不要在一個線程中創(chuàng)建局部引用并存儲到全局引用中愉粤,然后到另外一個線程去使用拿撩。
5.1.2 全局引用
全局引用可以跨方法、跨線程使用影暴,直到它被手動釋放才會失效探赫。同局部引用一樣期吓,全局引用也會阻止它所引用的對象被GC回收。
與局部引用可以被大多數(shù)JNI函數(shù)創(chuàng)建不同讨勤,全局引用只能使用一個JNI函數(shù)創(chuàng)建:NewGlobalRef潭千。下面這個版本的MyNewString演示了怎么樣使用一個全局引用:
/
This code is OK */
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; /
exception thrown /
}
/
Create a global reference /
stringClass = (
env)->NewGlobalRef(env, localRefCls);

     /* The local reference is no longer useful */
     (*env)->DeleteLocalRef(env, localRefCls);

     /* Is the global reference created successfully? */
     if (stringClass == NULL) {
         return NULL; /* out of memory exception thrown */
     }
 }
 ...

}
上面這段代碼中,一個由FindClass返回的局部引用被傳入NewGlobalRef屉来,用來創(chuàng)建一個對String類的全局引用。刪除localRefCls后茂契,我們檢查NewGlobalRef是否成功創(chuàng)建stringClass慨绳。
5.1.3 弱引用
弱引用使用NewGlobalWeakRef創(chuàng)建脐雪,使用DeleteGlobalWeakRef釋放。與全局引用類似战秋,弱引用可以跨方法脂信、線程使用。與全局引用不同的是梢薪,弱引用不會阻止GC回收它所指向的VM內部的對象尝哆。
在MyNewString中,我們也可以使用弱引用來存儲stringClass這個類引用琐馆,因為java.lang.String這個類是系統(tǒng)類恒序,永遠不會被GC回收歧胁。
當本地代碼中緩存的引用不一定要阻止GC回收它所指向的對象時,弱引用就是一個最好的選擇屠缭。假設崭参,一個本地方法mypkg.MyCls.f需要緩存一個指向類mypkg.MyCls2的引用,如果在弱引用中緩存的話奄喂,仍然允許mypkg.MyCls2這個類被unload:
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; /* can't find class /
}
myCls2 = NewWeakGlobalRef(env, myCls2Local);
if (myCls2 == NULL) {
return; /
out of memory /
}
}
... /
use myCls2 /
}
我們假設MyCls和MyCls2有相同的生命周期(例如跨新,他們可能被相同的類加載器加載),因為弱引用的存在蟹肘,我們不必擔心MyCls和它所在的本地代碼在被使用時,MyCls2這個類出現(xiàn)先被unload贰盗,后來又會preload的情況。
當然舵盈,真的發(fā)生這種情況時(MyCls和MyCls2的生命周期不同)秽晚,我們必須檢查緩存過的弱引用是指向活動的類對象,還是指向一個已經被GC給unload的類對象菩浙。下一節(jié)將告訴你怎么樣檢查弱引用是否活動句伶。
5.1.4 引用比較
給定兩個引用(不管是全局考余、局部還是弱引用),你可以使用IsSameObject來判斷它們兩個是否指向相同的對象疫蔓。例如:
(
env)->IsSameObject(env, obj1, obj2)
如果obj1和obj2指向相同的對象身冬,上面的調用返回JNI_TRUE(或者1)吏恭,否則返回JNI_FALSE(或者0)。
JNI中的一個引用NULL指向JVM中的null對象哀九。如果obj是一個局部或者全局引用阅束,你可以使用(env)->IsSameObject(env, obj, NULL)或者obj == NULL來判斷obj是否指向一個null對象。
在這一點兒上蝇更,弱引用有些有同,一個NULL弱引用同樣指向一個JVM中的null對象年扩,但不同的是,在一個弱引用上面使用IsSameObject時,返回值的意義是不同的:
(
env)->IsSameObject(env, wobj, NULL)
上面的調用中厨幻,如果wobj已經被回收,會返回JNI_TRUE况脆,如果wobj仍然指向一個活動對象,會返回JNI_FALSE批糟。
5.2 釋放引用
每一個JNI引用被建立時,除了它所指向的JVM中的對象以外徽鼎,引用本身也會消耗掉一個數(shù)量的內存盛末。作為一個JNI程序員,你應該對程序在一個給定時間段內使用的引用數(shù)量十分小心满败。短時間內創(chuàng)建大量不會被立即回收的引用會導致內存溢出。
5.2.1 釋放局部引用
大部分情況下叹括,你在實現(xiàn)一個本地方法時不必擔心局部引用的釋放問題汁雷,因為本地方法被調用完成后侠讯,JVM會自動回收這些局部引用厢漩。盡管如此,以下幾種情況下架谎,為了避免內存溢出,JNI程序員應該手動釋放局部引用:
1会涎、 在實現(xiàn)一個本地方法調用時末秃,你需要創(chuàng)建大量的局部引用蛔溃。這種情況可能會導致JNI局部引用表的溢出,所以,最好是在局部引用不需要時立即手動刪除涧衙。比如弧哎,在下面的代碼中,本地代碼遍歷一個大的字符串數(shù)組序攘,每遍歷一個元素程奠,都會創(chuàng)建一個局部引用,當對這個元素的遍歷完成時距境,這個局部引用就不再需要了晶姊,你應該手動釋放它:
for (i = 0; i < len; i++) {
jstring jstr = (env)->GetObjectArrayElement(env, arr, i);
... /
process jstr /
(
env)->DeleteLocalRef(env, jstr);
}
2们衙、 你想寫一個工具函數(shù),這個函數(shù)被誰調用你是不知道的忆蚀。4.3節(jié)中的MyNewString演示了怎么樣在工具函數(shù)中使用引用后,使用DeleteLocalRef刪除欣鳖。不這樣做的話泽台,每次MyNewString被調用完成后,就會有兩個引用仍然占用空間蜕依。
3、 你的本地方法不會返回任何東西吹缔。例如茶没,一個本地方法可能會在一個事件接收循環(huán)里面被調用,這種情況下笛求,為了不讓局部引用累積造成內存溢出,手動釋放也是必須的蜂嗽。
4、 你的本地方法訪問一個大對象病附,因此創(chuàng)建了一個對這個大對象的引用。然后本地方法在返回前會有一個做大量的計算過程较剃,而在這個過程中是不需要前面創(chuàng)建的對大對象的引用的写穴。但是偿短,計算過程,對大對象的引用會阻止GC回收大對象婆排。
在下面的程序中,因為預先有一個明顯的DeleteLocalRef操作,在函數(shù)lengthyComputation的執(zhí)行過程中炕婶,GC可能會釋放由引用lref指向的對象锁施。
5.2.2 管理局部引用
JDK提供了一系列的函數(shù)來管理局部引用的生命周期肩狂。這些函數(shù)包括:EnsureLocalCapacity、NewLocalRef审磁、PushLocalFrame费什、PopLocalFrame瘩蚪。
JNI規(guī)范中指出,VM會確保每個本地方法可以創(chuàng)建至少16個局部引用琢锋。經驗表明,這個數(shù)量已經滿足大多數(shù)不需要和JVM中的內部對象有太多交互的本地方法。如果真的需要創(chuàng)建更多的引用,本地方法可以通過調用EnsureLocalCapacity來支持更多的局部引用。在下面的代碼中,對前面的例子做了些修改猩系,不考慮內存因素的情況下疗涉,它可以為創(chuàng)建大量的局部引用提供足夠的空間。
· /* The number of local references to be created is equal to
· the length of the array. /
· if ((
env)->EnsureLocalCapacity(env, len)) < 0) {
· ... /* out of memory /
· }
· for (i = 0; i < len; i++) {
· jstring jstr = (
env)->GetObjectArrayElement(env, arr, i);
· ... /* process jstr /
· /
DeleteLocalRef is no longer necessary /
· }
當然武学,上面這個版本中沒有立即刪除不使用的局部引用硼补,因此會比前面的版本消耗更多的內存。
另外,Push/PopLocalFrame函數(shù)對允許程序員創(chuàng)建作用范圍層層嵌套的局部引用。例如,我們可以把上面的代碼重寫:
· #define N_REFS ... /
the maximum number of local references
· used in each iteration /
· for (i = 0; i < len; i++) {
· if ((
env)->PushLocalFrame(env, N_REFS) < 0) {
· ... /* out of memory /
· }
· jstr = (
env)->GetObjectArrayElement(env, arr, i);
· ... /* process jstr /
· (
env)->PopLocalFrame(env, NULL);
· }
PushLocalFrame為一定數(shù)量的局部引用創(chuàng)建了一個使用堆棧氮墨,而PopLocalFrame負責銷毀堆棧頂端的引用。
Push/PopLocalFrame函數(shù)對提供了對局部引用的生命周期更方便的管理。上面的例子中,如果處理jstr的過程中創(chuàng)建了局部引用苛萎,則PopLocalFrame執(zhí)行時腌歉,這些局部引用全部會被銷毀馍驯。
當你寫一個會返回局部引用的工具函數(shù)時玛痊,NewLocalRef非常有用,我們會在5.3節(jié)中演示NewLocalRef的使用狂打。
本地代碼可能會創(chuàng)建大量的局部引用擂煞,其數(shù)量可能會超過16個或PushLocaFrame和EnsureLocalCapacity調用設置的個數(shù)。VM可能會嘗試分配足夠的內存菱父,但不能夠保證分配成功颈娜。如果失敗,VM會退出浙宜。
5.2.3 釋放全局引用
當你的本地代碼不再需要一個全局引用時官辽,你應該調用DeleteGlobalRef來釋放它。如果你沒有調用這個函數(shù)粟瞬,即使這個對象已經沒用了同仆,JVM也不會回收這個全局引用所指向的對象。
當你的本地代碼不再需要一個弱引用時裙品,應該調用DeleteWeakGlobalRef來釋放它俗批,如果你沒有調用這個函數(shù),JVM仍會回收弱引用所指向的對象市怎,但弱引用本身在引用表中所占的內存永遠也不會被回收岁忘。
5.3 管理引用的規(guī)則
前面已經做了一個全面的介紹,現(xiàn)在我們可以總結一下JNI引用的管理規(guī)則了区匠,目標就是減少內存使用和對象被引用保持而不能釋放干像。
通常情況下,有兩種本地代碼:直接實現(xiàn)本地方法的本地代碼和可以被使用在任何環(huán)境下的工具函數(shù)驰弄。
當編寫實現(xiàn)本地方法的本地代碼時麻汰,當心不要造成全局引用和弱引用的累加,因為本地方法執(zhí)行完畢后戚篙,這兩種引用不會被自動釋放五鲫。
當編寫一個工具函數(shù)的本地代碼時,當心不要在函數(shù)的調用軌跡上面遺漏任何的局部引用岔擂,因為工具函數(shù)被調用的場合是不確定的位喂,一旦被大量調用,很有可能造成內存溢出乱灵。
編寫工具函數(shù)時忆某,請遵守下面的規(guī)則:
1、 一個返回值為基本類型的工具函數(shù)被調用時阔蛉,它決不能造成局部、全局癞埠、弱引用不被回收的累加状原。
2聋呢、 當一個返回值為引用類型的工具函數(shù)被調用時,它除了返回的引用以外颠区,它決不能造成其它局部削锰、全局、弱引用的累加毕莱。
對工具函數(shù)來說器贩,為了使用緩存技術而創(chuàng)建一些全局引用或者弱引用是正常的。
如果一個工具函數(shù)返回一個引用朋截,你應該詳細說明返回的引用的類型蛹稍,以便于調用者更好地管理它們。下面的代碼中部服,頻繁地調用工具函數(shù)GetInfoString唆姐,我們需要知道GetInfoString返回的引用的類型,以便于在每次使用完成后可以釋放掉它:
· while (JNI_TRUE) {
· jstring infoString = GetInfoString(info);
· ... /* process infoString /
·
· ??? /
we need to call DeleteLocalRef, DeleteGlobalRef,
· or DeleteWeakGlobalRef depending on the type of
· reference returned by GetInfoString. /
· }
函數(shù)NewLocalRef有時被用來確保一個工具函數(shù)返回一個局部引用廓八。為了演示這個用法奉芦,我們對MyNewString函數(shù)做了一些修改。下面的版本把一個被頻繁調用的字符串“CommonString” 緩存在了全局引用里:
· jstring
· MyNewString(JNIEnv env, jchar chars, jint len)
· {
· static jstring result;
·
· /
wstrncmp compares two Unicode strings /
· if (wstrncmp("CommonString", chars, len) == 0) {
· /
refers to the global ref caching "CommonString" /
· static jstring cachedString = NULL;
· if (cachedString == NULL) {
· /
create cachedString for the first time /
· jstring cachedStringLocal = ... ;
· /
cache the result in a global reference /
· cachedString =
· (
env)->NewGlobalRef(env, cachedStringLocal);
· }
· return (
env)->NewLocalRef(env, cachedString);
· }
·
· ... /
create the string as a local reference and store in
· result as a local reference /
· return result;
· }
在管理局部引用的生命周期中剧蹂,Push/PopLocalFrame是非常方便的声功。你可以在本地函數(shù)的入口處調用PushLocalFrame,然后在出口處調用PopLocalFrame宠叼,這樣的話先巴,在函數(shù)對中間任何位置創(chuàng)建的局部引用都會被釋放。而且车吹,這兩個函數(shù)是非常高效的筹裕,強烈建議使用它們。
如果你在函數(shù)的入口處調用了PushLocalFrame窄驹,記住在所有的出口(有return出現(xiàn)的地方)調用PopLocalFrame朝卒。在下面的代碼中,對PushLocalFrame的調用只有一次乐埠,但對PopLocalFrame的調用卻需要多次抗斤。
· jobject f(JNIEnv env, ...)
· {
· jobject result;
· if ((
env)->PushLocalFrame(env, 10) < 0) {
· /
frame not pushed, no PopLocalFrame needed /
· return NULL;
· }
· ...
· result = ...;
· if (...) {
· /
remember to pop local frame before return /
· result = (
env)->PopLocalFrame(env, result);
· return result;
· }
· ...
· result = (env)->PopLocalFrame(env, result);
· /
normal return */
· return result;
· }
上面的代碼同樣演示了函數(shù)PopLocalFrame的第二個參數(shù)的用法。局部引用result一開始在PushLocalFrame創(chuàng)建的當前frame里面被創(chuàng)建丈咐,而把result傳入PopLocalFrame中時瑞眼,PopLocalFrame在彈出當前的frame前,會由result生成一個新的局部引用棵逊,再把這個新生成的局部引用存儲在上一個frame當中伤疙。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子徒像,更是在濱河造成了極大的恐慌黍特,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,907評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件锯蛀,死亡現(xiàn)場離奇詭異灭衷,居然都是意外死亡,警方通過查閱死者的電腦和手機旁涤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評論 3 395
  • 文/潘曉璐 我一進店門翔曲,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人劈愚,你說我怎么就攤上這事瞳遍。” “怎么了造虎?”我有些...
    開封第一講書人閱讀 164,298評論 0 354
  • 文/不壞的土叔 我叫張陵傅蹂,是天一觀的道長。 經常有香客問我算凿,道長份蝴,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,586評論 1 293
  • 正文 為了忘掉前任氓轰,我火速辦了婚禮婚夫,結果婚禮上,老公的妹妹穿的比我還像新娘署鸡。我一直安慰自己案糙,他們只是感情好,可當我...
    茶點故事閱讀 67,633評論 6 392
  • 文/花漫 我一把揭開白布靴庆。 她就那樣靜靜地躺著时捌,像睡著了一般。 火紅的嫁衣襯著肌膚如雪炉抒。 梳的紋絲不亂的頭發(fā)上奢讨,一...
    開封第一講書人閱讀 51,488評論 1 302
  • 那天,我揣著相機與錄音焰薄,去河邊找鬼拿诸。 笑死,一個胖子當著我的面吹牛塞茅,可吹牛的內容都是我干的亩码。 我是一名探鬼主播,決...
    沈念sama閱讀 40,275評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼野瘦,長吁一口氣:“原來是場噩夢啊……” “哼描沟!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,176評論 0 276
  • 序言:老撾萬榮一對情侶失蹤啊掏,失蹤者是張志新(化名)和其女友劉穎蠢络,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體迟蜜,經...
    沈念sama閱讀 45,619評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,819評論 3 336
  • 正文 我和宋清朗相戀三年啡省,在試婚紗的時候發(fā)現(xiàn)自己被綠了娜睛。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,932評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡卦睹,死狀恐怖畦戒,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情结序,我是刑警寧澤障斋,帶...
    沈念sama閱讀 35,655評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站徐鹤,受9級特大地震影響垃环,放射性物質發(fā)生泄漏。R本人自食惡果不足惜返敬,卻給世界環(huán)境...
    茶點故事閱讀 41,265評論 3 329
  • 文/蒙蒙 一遂庄、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧劲赠,春花似錦涛目、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至塑煎,卻和暖如春沫换,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背轧叽。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評論 1 269
  • 我被黑心中介騙來泰國打工苗沧, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人炭晒。 一個月前我還...
    沈念sama閱讀 48,095評論 3 370
  • 正文 我出身青樓待逞,卻偏偏與公主長得像,于是被迫代替她去往敵國和親网严。 傳聞我的和親對象是個殘疾皇子识樱,可洞房花燭夜當晚...
    茶點故事閱讀 44,884評論 2 354

推薦閱讀更多精彩內容