- Java 與JNI 內(nèi)存管理是怎樣的
想要弄清楚Java與JNI的內(nèi)存管理的關(guān)系,首先要弄清楚JVM的內(nèi)存模型

其中本地方法棧就是運(yùn)行時(shí)調(diào)用native 方法的數(shù)據(jù)保存區(qū)。
本地方法棧的大小可以設(shè)置成固定的或者是動(dòng)態(tài)擴(kuò)展。
- Java中的內(nèi)存泄露
JAVA 編程中的內(nèi)存泄漏,從泄漏的內(nèi)存位置角度可以分為兩種:JVM 中 Java Heap 的內(nèi)存泄漏;JVM 內(nèi)存中 native memory 的內(nèi)存泄漏。
-
Java Heap 的內(nèi)存泄漏
- Java 對象存儲在 JVM 進(jìn)程空間中的 Java Heap 中,Java Heap 可以在 JVM 運(yùn)行過程中動(dòng)態(tài)變化。如果 Java 對象越來越多,占據(jù) Java Heap 的空間也越來越大,JVM 會(huì)在運(yùn)行時(shí)擴(kuò)充 Java Heap 的容量。如果 Java Heap 容量擴(kuò)充到上限,并且在 GC 后仍然沒有足夠空間分配新的 Java 對象,便會(huì)拋出 out of memory 異常,導(dǎo)致 JVM 進(jìn)程崩潰。
- Java Heap 中 out of memory 異常的出現(xiàn)有兩種原因:①程序過于龐大,致使過多 Java 對象的同時(shí)存在;②程序編寫的錯(cuò)誤導(dǎo)致 Java Heap 內(nèi)存泄漏。多種原因可能導(dǎo)致 Java Heap 內(nèi)存泄漏。JNI 編程錯(cuò)誤也可能導(dǎo)致 Java Heap 的內(nèi)存泄漏。
-
JVM 中 native memory 的內(nèi)存泄漏
JVM 進(jìn)程空間中,Java Heap 以外的內(nèi)存空間稱為 JVM 的 native memory。進(jìn)程的很多資源都是存儲在 JVM 的 native memory 中,例如載入的代碼映像,線程的堆棧,線程的管理控制塊,JVM 的靜態(tài)數(shù)據(jù)、全局?jǐn)?shù)據(jù)等等。也包括 JNI 程序中 native code 分配到的資源。JNI編程中的內(nèi)存泄露
Native Code 本身的內(nèi)存泄漏
Global Reference 引入的內(nèi)存泄漏
JNI 編程中潛在的內(nèi)存泄漏,特別是LocalReference的使用
-
哪些情況下需要做內(nèi)存管理?
- native code本身需要做內(nèi)存管理
如使用c/c++語言編寫,需要遵守語言本身的內(nèi)存管理策略,如指針變量的創(chuàng)建于釋放不當(dāng)都可能產(chǎn)生內(nèi)存泄露,進(jìn)一步導(dǎo)致內(nèi)存JVM的崩潰。- Global Reference的管理
而 Global Reference 對 Java 對象的引用一直有效,因此它們引用的 Java 對象會(huì)一直存在 Java Heap 中。在使用 Global Reference 時(shí),需要仔細(xì)維護(hù)對 Global Reference 的使用。如果一定要使用 Global Reference,務(wù)必確保在不用的時(shí)候刪除。就像在 C 語言中,調(diào)用 malloc() 動(dòng)態(tài)分配一塊內(nèi)存之后,調(diào)用 free() 釋放一樣。否則,Global Reference 引用的 Java 對象將永遠(yuǎn)停留在 Java Heap 中,造成 Java Heap 的內(nèi)存泄漏。-
Local Reference的管理
Local Reference管理模型圖
localReference_Java_map.jpg
從這個(gè)映射關(guān)系表可以看出,實(shí)際上,每當(dāng)線程從 Java 環(huán)境切換到 native code 上下文時(shí)(J2N),JVM 會(huì)分配一塊內(nèi)存,創(chuàng)建一個(gè) Local Reference 表,這個(gè)表用來存放本次 native method 執(zhí)行中創(chuàng)建的所有的 Local Reference。每當(dāng)在 native code 中引用到一個(gè) Java 對象時(shí),JVM 就會(huì)在這個(gè)表中創(chuàng)建一個(gè) Local Reference:
運(yùn)行 native method 的線程的堆棧記錄著 Local Reference 表的內(nèi)存位置(指針 p)
Local Reference 表中存放 JNI Local Reference,實(shí)現(xiàn) Local Reference 到 Java 對象的映射。
native method 代碼間接訪問 Java 對象(java obj1,java obj2)。通過指針 p 定位相應(yīng)的 Local Reference 的位置,然后通過相應(yīng)的 Local Reference 映射到 Java 對象。
當(dāng) native method 引用一個(gè) Java 對象時(shí),會(huì)在 Local Reference 表中創(chuàng)建一個(gè)新 Local Reference。在 Local Reference 結(jié)構(gòu)中寫入內(nèi)容,實(shí)現(xiàn) Local Reference 到 Java 對象的映射。
native method 調(diào)用 DeleteLocalRef() 釋放某個(gè) JNI Local Reference 時(shí),首先通過指針 p 定位相應(yīng)的 Local Reference 在 Local Ref 表中的位置,然后從 Local Ref 表中刪除該 Local Reference,也就取消了對相應(yīng) Java 對象的引用(Ref count 減 1)。
當(dāng)越來越多的 Local Reference 被創(chuàng)建,這些 Local Reference 會(huì)在 Local Ref 表中占據(jù)越來越多內(nèi)存。當(dāng) Local Reference 太多以至于 Local Ref 表的空間被用光,JVM 會(huì)拋出異常,從而導(dǎo)致 JVM 的崩潰。
Local Reference 不是native code的局部變量,區(qū)別體現(xiàn)在:
局部變量存儲在線程堆棧中,而 Local Reference 存儲在 Local Ref 表中。
局部變量在函數(shù)退棧后被刪除,而 Local Reference 在調(diào)用 DeleteLocalRef() 后才會(huì)從 Local Ref 表中刪除,并且失效,或者在整個(gè) Native Method 執(zhí)行結(jié)束后被刪除。
可以在代碼中直接訪問局部變量,而 Local Reference 的內(nèi)容無法在代碼中直接訪問,必須通過 JNI function 間接訪問。JNI function 實(shí)現(xiàn)了對 Local Reference 的間接訪問,JNI function 的內(nèi)部實(shí)現(xiàn)依賴于具體 JVM。
具體關(guān)于JNI內(nèi)存泄露的實(shí)例分析可以參考IBM開發(fā)者社區(qū)的一篇文章:在 JNI 編程中避免內(nèi)存泄漏
