標籤:
昨天朋友問我,如果一個java局部對象在調用jni的時候,如果java層沒有引用它,這個對象會不會因為被jni層引用不被GC,導致記憶體流失。我大概想了一下,說不會。當時想的很簡單,c裡面沒有像java一樣的類似的記憶體回收機制,java層進入jni時值傳遞,不會導致引用產生。實事上比想象的複雜的多,而且並不是這樣的。
按照這種想法,如果java層的對象被jni引用時不會計數,對象被GC時,jni層會產生野指標。事實並不是這樣。
首先GC是發生在進程範圍內的堆空間,如果堆裡的對象沒有被引用,是要被GC回收的。jni層也會在堆上建立對象,照樣能被GC。
java的記憶體回收可以用表示。
如果heap裡面的對象沒有被Stack引用到,即紅色的地區,是要被回收的。總的來說,只要對象沒有被GC root直接或間接引用到就會被GC。
jni調用產生時,jvm的記憶體分布是這個樣子的。
它有兩個棧,一個是java棧,另外一個是native 棧。當一個java調用jni時,棧幀布局其實是這個樣子。
這個是棧幀,所示,該線程首先調用了兩個Java方法,而第二個Java方法又調用了一個本地方法,這樣導致虛擬機器使用了一個本地方法棧。圖中的本地方法棧顯示為 一個連續的記憶體空間。假設這是一個C語言棧,期間有兩個C函數,他們都以包圍在虛線中的灰色塊表示。第一個C函數被第二個Java方法當做本地方法調用, 而這個C函數又調用了第二個C函數。之後第二個C函數被第二個Java方法當做本地方法調用,而這個C函數又調用了第二個C函數。之後第二個C函數又通過 本地方法介面回調了一個Java方法(第三個Java方法)。最終這個Java方法又調用了一個Java方法(他成為圖中的當前方法) 。
剛才說到只要對象沒有被GC root直接或間接引用到就會被GC。GC root到底是什麼呢?
JVM對那些沒有根引用的對象進行來及回收,也就是無法從根對象中追述的對象。
JVM記憶體回收的根對象的範圍有以下幾種:
1、棧中引用的對象,引用是在棧幀中的本地變數表中的,真正的對象在堆中
2、方法區perm中的類靜態屬性引用的對象,以及常量引用的對象
3、本地方法棧中JNI(Native方法)的引用的對象
所以jni層,C函數調用C函數傳遞jobject對象時,對象的引用會被作為參數壓入到本地方法棧中,會從本地方法棧(GC root的一種)產生引用,這麼就不會被gc了。同樣java層和jni層互相調用時,總會被GC root引用到,也不會被GC 回收了。
android 記憶體回收