GC可能隨時決定它需要對垃圾收集的堆做壓縮工作忌穿。壓縮涉及將一個對象物理地從一個地址轉(zhuǎn)移到另一個地址。這些對象可能被一個JNI本地引用或者全局引用所持有。為了使得壓縮安全的進(jìn)行雹仿,JNI的引用不會直接指向堆重付。至少有一個間接層將本地代碼和對象移動隔離開來。
如果本地代碼需要對一個對象內(nèi)部直接尋址障贸,解決方案會是十分復(fù)雜错森。直接對堆進(jìn)行尋址或者固定堆,通常是因?yàn)槟阈枰凑找环N快速的篮洁、共享的方式訪問一個有大量數(shù)據(jù)的原始數(shù)組涩维。一個常用的場景是,當(dāng)你使用屏幕緩沖區(qū)袁波。在這些場景中你可以使用JNI臨界區(qū)段來實(shí)現(xiàn)這種方式瓦阐,但是正如JNI對這些方法的描述那樣,這種使用方式對開發(fā)者提出了更高的要求篷牌。具體描述可以參看JNI相關(guān)描述睡蟋。
- GetPrimitiveArrayCritical 返回一個Java數(shù)組的直接堆地址,并且禁用垃圾收集直到對應(yīng)的ReleasePrimitiveArrayCritical函數(shù)調(diào)用枷颊。
- GetStringCritical 返回一個String實(shí)例的直接堆地址戳杀,并且禁止垃圾收集直到ReleaseStringCritical被調(diào)用。
所有其他形如 Get<原始類型>ArrayElements 接口返回的都是一個副本夭苗,不會受到壓縮的影響信卡。
當(dāng)你使用的是平衡垃圾收集策略的時候,形如*Critical這種形式的調(diào)用可能不會返回一個堆內(nèi)部的直接指針题造,返回結(jié)果受到isCopy標(biāo)志位的影響傍菇。導(dǎo)致這種結(jié)果是因?yàn)樵谝粋€較大的數(shù)組內(nèi)部,其數(shù)據(jù)不一定是連續(xù)存儲的界赔。通常桥嗤,當(dāng)一個數(shù)組的容量小于堆大小的1/1000的時候,返回的是一個直接指針仔蝌。
使用isCopy標(biāo)志位
JNI Get<Type> 函數(shù)指定一個pass-by-reference輸出參數(shù)(jboolean *isCopy)泛领,這個參數(shù)允許開發(fā)者決定一個JNI調(diào)用返回一個對象副本的地址還是一個堆上固定對象的地址。
Get<Type> 和 Release<Type> 函數(shù)需要成對出現(xiàn)敛惊。
- GetStringChars and ReleaseStringChars
- GetStringCritical and ReleaseStringCritical
- GetStringUTFChars and ReleaseStringUTFChars
- Get<PrimitiveType>ArrayElements and Release<PrimitiveType>ArrayElements
- GetPrimitiveArrayCritical and ReleasePrimitiveArrayCritical
如果你傳入一個非空的地址作為isCopy的參數(shù)渊鞋,JNI函數(shù)就會將傳入地址的jboolean設(shè)置為JNI_TRUE,這樣返回的是數(shù)組元素的一個副本。反之锡宋,設(shè)置jboolean為JNI_FALSE儡湾,這樣返回的是堆上固定對象的地址指針。
除非你使用臨界函數(shù)(譯者注:critical functions)执俩,J9的虛擬機(jī)總是返回的是一個副本徐钠。使用副本可以減輕GC的負(fù)擔(dān),因?yàn)楣潭▽ο蟛荒鼙粔嚎s役首,而且會產(chǎn)生碎片整理復(fù)雜尝丐。
為了避免內(nèi)存泄漏,你必須:
- 你自己使用Get<Type>Region和Set<Type>Region函數(shù)管理由于復(fù)制產(chǎn)生的內(nèi)存衡奥。
- 確保那些使用Get<Type>函數(shù)產(chǎn)生的內(nèi)存爹袁,當(dāng)它們不再需要使用的時候,使用Release<Type>進(jìn)行釋放矮固。
使用mode標(biāo)志位
當(dāng)你調(diào)用Release<Type>ArrayElements失息,最后一個參數(shù)就是mode標(biāo)志位。當(dāng)你使用的是一個復(fù)制的數(shù)組的時候档址,這個模式標(biāo)志位可以用來避免向Java堆的沒有必要的拷貝盹兢。如果你使用的是一個固定數(shù)組(譯者注:直接指向堆上的數(shù)組,不是通過copy形式)守伸,那么這個標(biāo)志位將被忽略蛤迎。
無論isCopy的參數(shù)值是怎么樣的,你必須在每個Get<Type>調(diào)用之后相應(yīng)的調(diào)用Release<Type>含友。這種使用方式是必須的,因?yàn)檎{(diào)用Release<Type>會刪除可能會影響到垃圾收集的JNI本地引用校辩。
mode標(biāo)志位的可能值如下:
- 0 更新Java堆上的數(shù)據(jù)窘问。釋放副本使用的空間。
- JNI_COMMIT 更新Java堆上的數(shù)據(jù)宜咒。不釋放副本使用的空間惠赫。
- JNI_ABORT 不更新Java堆上的數(shù)據(jù)。釋放副本使用的空間故黑。
通常對于Release<Type>的調(diào)用儿咱,‘0’模式位是最安全的選擇及無論數(shù)據(jù)的副本變化與否,堆都會使用副本來更新场晶,并且也不會有泄漏混埠。
可以使用JNI_ABORT標(biāo)志值來避免對一個未變化的副本向Java堆的反向拷貝。如果你修改了返回的數(shù)組诗轻,你需要在使用JNI_ABORT標(biāo)志值回滾這些變化之前檢查一下isCopy標(biāo)志位钳宪。
這個步驟是必須的,因?yàn)楹鸵粋€復(fù)制JVM相比,一個固定JVM離開堆是在不同的狀態(tài)吏颖。
使用isCopy標(biāo)志位和mode標(biāo)志位的通用方式:
這里有一個通用的方式來使用isCopy標(biāo)志位和mode標(biāo)志位搔体。這種方式確保對所有的JVM都有用,并且確保改變被提交半醉,不會發(fā)生泄漏疚俱。
為了使用通常的使用方式,你需要確保:
- 不要使用isCopy標(biāo)志位缩多,傳入null或者0
- 總是設(shè)置mode標(biāo)志位為0
只有當(dāng)你做優(yōu)化的時候才會使用到這些標(biāo)志位的復(fù)雜用法呆奕。當(dāng)你使用這種通用的方式時,你仍然需要考慮到同步問題瞧壮〉锹可以參考Synchronization.