JNI編程經驗(JNI Tips)

轉載自:http://blog.csdn.net/nicebooks/article/details/17925521

JNI編程經驗(JNI Tips) .

翻譯原文來自:http://developer.android.com/intl/zh-cn/training/articles/perf-jni.html

JNI全稱是Java Native Interface, 它是一種使用java語言和原生C/C++語言相互調用,混合編程的方法. 它支持從動態鏈接庫中加載代碼, 並能使用C/C++的高效的特性

如果你之前對這個還不熟悉, 完整的讀一遍Java Native Interface文檔(http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/jniTOC.html)可以對JNI的功能有個基本的瞭解. 有些功能在你讀第一遍時還沒什麼特別的感覺, 但你可能會發現有些功能使用起來會很便利.

Java虛擬機和JNIEnv


JNI定義了2個數據結構, java虛擬機和JNIEnv, 這兩個本質上都是指向函數表的指針. (在C++版本中, 他們是2個類, 包含了成員函數, 這些成員函數都是JNI的函數.) java虛擬機提供”調用接口”函數, 它允許你創建和銷燬java虛擬機, 理論上你可以在一個進程裏有多個java虛擬機. 但android只允許1個.

JNIEnv提供了大部分的JNI函數, 在C語言裏, 你所有的native函數都要接收JNIEnv作爲第一個參數.

JNIEnv用在線程本身的存儲, 這樣會導致你不能在多個線程裏共享JNIEnv, 如果部分代碼沒有辦法獲得JNIEnv, 那麼可以把java虛擬機共享, 通過java虛擬機來獲得這個線程的JNIEnv.

C語言聲明的JavaVM和JNIEnv與C++聲明的不同, “jni.h”頭文件裏使用宏定義把C和C++的聲明作了區別. 基於這原因, 把JNIEnv作爲參數放在頭文件裏是不好的方式, 這導致在C和C++都用這個頭文件時會有很大的麻煩.(也可以這麼說: 如果你的頭文件需要定義#ifdef __cplusplus, 那麼你必須對這個指向JNIEnv的頭文件作一些改動來同時適應C和C++語言.

線程


JNI裏的所有的線程都是linux的線程, 由系統內核進行調度, 他們通常由Thread.start來啓動, 但也可以在通過關聯上Java虛擬機後在native語言中啓動。 例如,使用C語言的pthread_create也可以創建線程,但在使用JNI方法AttachCurrentThread或AttachCurrentThreadAsDaemon來關聯之前, 這個線程是沒有JNIEnv的。同時也不能調用JNI的接口。

關聯上native語言創建的線程會導致一個java.lang.Thread對象被創建同時添加到”main”線程組裏,這樣也可以被調試器檢測到。如果再使用AttachCurrentThread來關聯一個已經被關聯的線程將不會有任何動作。

Android不能暫停線程去執行native的代碼, 如果一個GC在運行或者調試器觸發了一個暫停請求,Android將會暫停在下一個JNI的調用,而不是當前的native代碼。

使用AttachCurrentThread或AttachCurrentThreadAsDaemon關聯到JNI的線程在結束前必須調用DetachCurrentThread來解除關聯。假如直接寫DetachCurrentThread來取消關聯會比較彆扭,在android2.0之後,你可以用pthread_key_create來定義一個線程的析構方法,並且在這個析構方法裏調用DetachCurrentThread。(使用pthread_setspecific來把JNIEnv保存在線程本地的存儲裏, 這個將會作爲參數傳到析構方法裏.)

jclass, jmethodID和jfieldID


如果你想在native代碼裏訪問一個對象的屬性,你需要按照以下幾步來做:

• 使用FindClass獲取類對象的引用

• 使用GetFieldID獲取屬性的ID

• 使用恰當的方法來獲取屬性的值,例如GetIntField

類似的,如果要調用一個方法,你首先需要獲得類對象的引用, 然後是方法的ID,這些ID通常是指向內部運行的數據結構,要獲得這些ID可能需要多次字符串的比較,但是一旦你獲得這些ID,那麼調用這些方法或者獲取屬性將會非常快。

如果你追求高性能,最好是做一次查找,然後這些ID存起來,由於android有每個進程只能有一個Java虛擬機的限制,這是個很好的理由把這些數據存在本地。

類的引用,屬性的ID和方法的ID能保證在類卸載前一直有效。類只有在GC時沒有發現任何使用者時纔可能卸載,這個在android中基本不可能發生。不管怎麼樣,jclass作爲類引用必須使用NewGlobalRef來保護。

如果你喜歡在加載一個類時把那些ID保存起來, 並且在類卸載和重新加載時重新保存他們,正確的方法是像下面的代碼這樣來初始化這些ID。

/*

  • We use a class initializer to allow the native code to cache some

  • field offsets. This native function looks up and caches interesting

  • class/field/method IDs. Throws on failure.

    */

    private static native void nativeInit();

    static {

nativeInit();

}

臨時和全局引用


每個傳給原生方法的參數,和幾乎所有由JNI方法返回的對象都是臨時引用,這意味這個引用的有效期與當前原生方法在當前線程的執行過程是一樣長的,所以在原生方法返回後,這個引用將不在有效。

這個規則也可以適用在所有jobject的子類,包括jclass, jstring和jarray.(如果擴展JNI檢測被設上後,在運行時會警告你大部分的引用使用錯誤)

只有使用NewGlobalRef 和 NewWeakGlobalRef可以獲得全局的引用。

如果你想保持一個引用更長的時間, 你必須使用”global”引用.NewGlobalRef方法接收一個臨時引用的參數,然後返回一個全局引用。全局引用一直有效,直到你使用DeleteGlobalRef來刪除它。

全局引用通常用來保存使用FindClass返回的jclass.例如:

jclass localClass = env->FindClass("MyClass");

jclass globalClass = reinterpret_cast<jclass>(env->NewGlobalRef(localClass));

所有JNI方法接受臨時和全局引用作爲參數。引用到同一個對象卻有多個值是可能的。例如,連續使用NewGlobalRef來引用同一個對象的返回值可能是不一樣的。要比較2個引用是不是指向同一個對象,必須使用IsSameObject方法來判斷,永遠不要在原生代碼中用==來判斷兩個引用是否一樣。

所以你不能假設原生代碼中對象的引用是常量或者唯一的。不能用jobject的值來作爲對象的key。

我們要求開發者不要過分的使用臨時引用,這意味着如果你創建了很多臨時引用,你最好使用DeleteLocalRef來釋放這些臨時引用,而不是讓JNI來做這事。因爲有的系統只允許使用16個臨時引用,濫用臨時引用會導致內存溢出。如果你需要更多的臨時引用,要麼你把之前的釋放掉,要麼使用EnsureLocalCapacity/PushLocalFrame/PopLocalFrame來增加臨時引用的最大數目。

注意:jfieldID和jmethodID是不透明的類型,而不是對象引用,所以不能用NewGlobalRef方法來調用。由某些方法例如GetStringUTFChars和GetByteArrayElements返回的原始數據指針也不是對象.(這些類型可以在線程間傳遞,並且在相應的對象釋放前一直有效.)

有一個不常見的用例值得單獨提到,如果你使用AttachCurrentThread關聯一個線程,線程中代碼生成的臨時引用要到線程取消關聯後纔會自動釋放.所以你需要手動釋放你創建的臨時引用,通常要遵循誰創建,誰釋放的規則。

UTF-8和UTF-16字符串


java語言用的是UTF-16編碼字符串. 爲了便利,JNI也提供了修改Mod UTF-8字符串的方法. 修改Mod編碼的方法在C語言中很有用,因爲它把\u0000編碼爲0xc0 0x80而不是0x00. 更好的是你可以處理\0結尾的C語言格式字符串, 這適用於那些使用標準libc的字符串函數.不好的一面是你不能隨意的把UTF-8數據傳給JNI就期望它能正常工作。

如果可能,一般處理UTF-16的字符串會比較快, Android現在不要求在GetStringChars中進行拷貝,而GetStringUTFChars要求一次分配內存和轉換成UTF-8字符串. 注意UTF-16字符串不是\0結尾的,並且\u0000是允許的。所以你需要跟jchar指針一樣獲取字符串的長度。

使用Get獲得的字符串記得要釋放. 這些字符串函數返回jchar*或者jbyte*, 這種C語言格式的指針指向原始數據比臨時引用好,他們保證在釋放前是有效的,這意味着在原生方法返回後他們還是有效的,未釋放的。

傳給NewStringUTF的參數必須是Mod UTF-8格式的字符串, 一個容易犯的錯誤是把從文件或網絡流中讀到字符串傳給NewStringUTF而沒有過濾他,除非你知道這個數據是7位ASCII,你需要去掉高位ASCII字符或者轉換他們爲正確的Mod UTF-8格式.如果你沒有這樣做,默認UTF-16的轉換應該不是你所期望的.擴展的JNI檢查會掃描字符串和警告是無效數據,但他們不會捕獲所有的異常。

原始數組


JNI提供了方法來方法數組對象的內容,但必須一次訪問一個數組對象的數據。原始數組可以像C語言那樣直接讀和寫。

爲了使接口高效而且不受虛擬機實現的制約, GetArrayElements系列函數允許返回一個指向實際元素的指針, 或者分配一些內存來拷貝數據. 無論哪種方式, 返回的原始數據的指針在調用釋放方法前是保證一直有效的(這意味着如果數據沒有被拷貝, 這個對象數組將被限制在壓縮堆數據時不能移動). 你必須自己釋放每個你獲取的數組. 同時如果Get方法失敗的話,你的代碼一定不能嘗試釋放一個NULL指針.

最有用的是, 你可以通過傳一個非空指針作爲isCopy的參數來決定是否拷貝數據.

那些釋放方法有一個mode參數, 這個參數有3個值, 根據這個參數, 這個方法在運行時會根據指針是指向原始數據還是拷貝的數據有不同的表現.

•0

◦原始數據: 對象數組將不會被限制.

◦拷貝數據: 數據將會拷貝回原始數據, 同時釋放拷貝數據.

•JNI_COMMIT

◦原始數據: 什麼都不作.

◦拷貝數據: 數據將會拷貝回原始數據, 不釋放拷貝數據.

•JNI_ABORT

◦原始數據: 對象數組將不會被限制, 之前的數據操作有效..

◦拷貝數據: 釋放拷貝數據, 之前的任何數據操作會丟棄.

如果你對對象數組做了修改, 並且你有可能會根據對象數組的內容來決定是修改還是繼續執行其他代碼, 那麼

檢查isCopy參數是你是否需要用JNI_COMMIT來調用釋放方法的一個理由, 你可以不做操作. 另一個可能的原因,檢查該參數是爲了使用JNI_ABORT的高效處理.例如, 你可能需要獲得一個數組, 臨時修改一下,然後將部分數據傳給其他方法, 最後丟棄之前的修改. 如果你知道JNI是作了拷貝數據的Get, 那就不需要另外創建一份拷貝來修改. 但如果JNI是把原始數據傳給你, 那麼你需要自己拷貝一份數據來進行修改.

一個最容易犯的錯誤是如果isCopy是false, 有人以爲可以不調用釋放方法(這在例子代碼中有很多這樣的錯誤). 因爲即使沒有拷貝數據, 原始數據也是被限制不能回收的.所以要主動調用釋放方法來釋放.

同時要注意JNI_COMMIT標誌不會釋放數組, 你終究需要再次用另外一個標誌來調用釋放方法.

局部方法


當你想對數組進行寫入或讀出, 這還有一個像GetArrayElements和GetStringChars非常有用的方式:

jbyte* data = env->GetByteArrayElements(array, NULL);    

if (data != NULL) {

memcpy(buffer, data, len);        

env->ReleaseByteArrayElements(array, data, JNI_ABORT);    

}

這個方式是獲取了數組, 然後拷貝了len長度的byte, 最後釋放掉數組. 根據上面的實現, Get方法會限制或者拷貝數據. 在單獨的拷貝數據後(也許就是第二次), 就調用釋放方法來釋放數據. 在這種情形下, JNI_ABORT保證這裏沒有機會有第三個拷貝.

還有一個更簡單實現同樣功能的方式:

env->GetByteArrayRegion(array, 0, len, buffer);

這種方式有幾個好處:

•只需要一個JNI調用而不是兩個, 減少開銷.

•不需要對原始數據進行限制或者額外的拷貝數據

•減少開發者的風險(不會有在某些出錯後忘記釋放的風險)

同樣的, 你可以用SetArrayRegion系列方法來拷貝數據到一個數組, GetStringRegion或者GetStringUTFRegion是從一個String中拷貝字符.

異常


你不能在有異常等待處理時調用大多數的JNI方法。你的代碼應該要預期到有異常(通過函數的返回值,ExceptionCheck或ExceptionOccurred)並返回,或清除異常並處理它。

你只可以在異常等待處理時調用下面的JNI方法:

•DeleteGlobalRef

•DeleteLocalRef

•DeleteWeakGlobalRef

•ExceptionCheck

•ExceptionClear

•ExceptionDescribe

•ExceptionOccurred

•MonitorExit

•PopLocalFrame

•PushLocalFrame

•ReleaseArrayElements

•ReleasePrimitiveArrayCritical

•ReleaseStringChars

•ReleaseStringCritical

•ReleaseStringUTFChars

很多JNI方法會跑出異常, 但通常會提供一個簡單的方法來檢查失敗. 例如,如果NewString返回一個非空值, 你不需要去檢查異常. 然而,如果你調用一個方法(比如CallObjectMethod), 你必須檢查異常, 因爲返回值在異常發生時是不正確的.

在解釋代碼拋出異常時不會放鬆本地的棧幀, android現在還不支持C++異常. JNI的Throw和ThrowNew只是在當前線程設置一個異常指針. 直到從原生代碼返回到java代碼, 異常纔會被拋出或者正確的處理.

原生代碼可以用ExceptionCheck或者ExceptionOccurred來捕獲一個異常, 並使用ExceptionClear來清除它. 通常丟棄異常沒有處理它們可能導致問題

這裏沒有內置的方法來控制Throwable對象, 所以如果你想獲得異常的描述, 你需要找到Throwable類, 查找它的 getMessage “()Ljava/lang/String;”方法的ID, 然後調用它, 如果結果不是空,那麼用GetStringUTFChars來獲取內容,這個內容可以用printf來顯示.

擴展的檢查


JNI會做一些錯誤檢查, 錯誤通常結果是崩潰. Android也提供了一個名爲CheckJNI模式, 其中的JavaVM與JNIEnv的函數表指針調用標準實現之前會切換到擴展系列的檢查功能表執行檢查。

附加的檢查包括:

•數組: 嘗試分配一個大小爲負值的數組

•錯誤的指針: 傳一個錯誤的jarray/jclass/jobject/jstring到JNI方法, 或者傳一個空指針連同一個非空的參數到JNI方法

•類名:傳一個不是“java/lang/String”風格的類名到JNI方法裏.

•重要的調用: 在一個重要的Get和Release之間調用一個JNI方法.

•直接的Byte緩存:傳一個錯誤的參數給NewDirectByteBuffer

•異常:在有異常等待處理時調用一個JNI方法

•JNIEnv*: 在不合適的線程中使用JNIEnv*

•jfieldIDs: 使用一個NULL jfieldID, 或者使用一個jfieldID來設置錯誤類型的值(試圖給一個String屬性賦StringBuilder的值), 或使用jfieldID的靜態字段設置一個實例字段或反之亦然, 或使用一個類中jfieldID到另一個類的實例裏.

•jmethodIDs:在調用JNI方法時使用錯誤的jmethodID類型: 不正確的返回類型, 靜態和非靜態類型的錯誤匹配, “this”的錯誤類型(非靜態調用)或者錯誤的類(靜態調用).

•References(引用):對不正確的引用使用DeleteGlobalRef/DeleteLocalRef.

•Release modes(釋放模式):傳一個錯誤的釋放模式來調用釋放方法(比如0, JNI_ABORT或者JNI_COMMIT).

•類型安全檢查:從原生方法返回一個不兼容的類型(從一個聲明返回值爲String的原生方法裏返回一個StringBuilder).

•UTF-8:傳一個無效的Mod UTF-8字節序列到JNI方法裏.

(方法和屬性的訪問限制不做檢查:訪問限制並不適用於原生代碼)

這裏有幾個方法來設置CheckJNI

如果你使用的是模擬器, CheckJNI默認是開啓的

如果你root了一個設備, 你可以用下列命令來重起runtime開啓CheckJNI:

adb shell stop

adb shell setprop dalvik.vm.checkjni true

adb shell start

在這兩種情況下,你將會在logcat中看到runtime啓動的log:

D AndroidRuntime: CheckJNI is ON

如果你有一個沒root的設備, 你可以用下列命令:

adb shell setprop debug.checkjni 1

這個不會影響已經運行的應用, 但從這之後運行的應用將是開啓CheckJNI的.(把這個值改爲其他任何值或者重起設備將會關閉CheckJNI.)這種情況下, 你會看到logcat在應用運行時顯示

D Late-enabling CheckJNI

原生庫


你可以使用標準System.loadLibrary來從共享庫里加載原生代碼. 推薦的實現代碼如下:

• 在類的靜態構造方法裏調用System.loadLibrary(見之前的例子, 那個使用nativeClassInit的例子.) 參數是未修飾庫名, 如果庫文件是”libfubar.so”, 那麼這裏傳”fubar”.

• 提供一個原生方法:jint JNI_OnLoad(JavaVM* vm, void reserved)

• 在JNI_OnLoad中, 註冊所有的原生方法. 你應該把這些方法聲明爲”static”, 這樣這些方法名就不會在設備符號表裏佔用空間.

JNI_OnLoad方法在C++中應該像下面這樣寫:

jint JNI_OnLoad(JavaVM* vm, void* reserved){

JNIEnv* env;

if (vm->GetEnv(reinterpret_cast

64位機器的注意事項


Android當前是主要爲在32位系統上運行而設計的. 理論上它也可以在64位系統上運行, 但目前這個不是Android的首要目標. 在64位機器很多地方你不需要擔心跟原生代碼的交互, 但如果你計劃在原生數據結構中保存指針, 那麼這就是一個要注意的問題.爲了支持使用64位的指針, 你需要把你的指針保存在long類型中, 而不是int類型.

不支持的特性和向後的兼容性


所有JNI 1.6的特性都支持, 包括下列異常:

•DefineClass沒有實現. Android不使用Java字節編碼或類文件, 所以傳入二進制類數據是不支持的.

爲了向後兼容舊的Android版本, 你需要注意的是:

•動態查找原生方法

直到Android2.0(Eclair), 在搜索方法名時, ‘$’字符無法正確轉換成”_00024”. 解決方案是使用顯示註冊方法或者把原生方法從內嵌裏移出來.

•取消關聯線程

直到Android2.0(Eclair),Android不支持使用pthread_key_create設置線程析構方法來避免”線程必須在退出前取消關聯”的檢查.(runtime也會使用線程析構方法, 所以這個比誰先獲得調用的競賽.)

•弱全局引用

直到Android2.2(Froyo), 弱全局引用沒有實現. 舊版本將會在使用它時提示拒絕. 你可以使用Android平臺版本常量來測試是否支持.

直到Android4.0(Ice Cream sandwich), 弱全局引用只能傳給NewLocalRef, NewGlobalRef和DeleteWeakGlobalRef.(文檔強烈建議開發者把弱全局引用賦給強引用後再開始做相應工作, 所以這不應該是在所有限制)

從Android4.0(Ice Cream sandwich)開始, 弱全局引用可以像其他JNI引用一樣使用.

臨時引用

直到Android4.0(Ice Cream sandwich), 臨時引用實際上就是直接指針. 4.0添加了必要的間接指針來支持更好的GC, 但這意味着很多JNI bug無法在舊版本上檢測到. 請看下面的鏈接獲取詳細信息 http://android-developers.blogspot.com/2011/11/jni-local-reference-changes-in-ics.html

•使用GetObjectRefType確定引用類型

直到Android4.0(Ice Cream sandwich),作爲使用直接指針的一個結果, 它是不可能正確實現GetObjectRefType的.相應的是,我們使用了啓發式,透過弱全局引用表, 參數, 臨時引用表和全局引用表的各個指針地址來查找引用類型. 當第一次找到直接指針, 它將會把他檢測到的類型返回給你. 這個意思是, 例如, 如果你對一個全局jclass調用GetObjectRefType, 恰好當時jclass傳了一個顯示參數到一個靜態原生方法, 你將會得到的返回值是JNILocalRefType而不是JNIGlobalRefType.

FAQ: 爲什麼會碰到UnsatisfiedLinkError錯誤?


當執行到原生代碼時, 你可能會碰到這個錯誤log:

java.lang.UnsatisfiedLinkError: Library foo not found

在某些情況下這意味着就是庫找不到. 其他情況是庫存在但不能被dlopen打開, 並且詳細的錯誤可以在異常的詳細信息讀到.

“library not found”異常發生的原因通常是:

•庫不存在或app沒權限訪問它. 使用adb shell ls -l 去檢查庫的狀態和訪問權限.

•庫不是由NDK編成的, 這可能會導致在沒有該設備上存在的庫函數的依賴關係。

還有一個UnsatisfiedLinkError的錯誤是這樣的:

java.lang.UnsatisfiedLinkError: myfunc

at Foo.myfunc(Native Method)

at Foo.main(Foo.java:10)

在logcat中, 你會看到:

W/dalvikvm( 880): No implementation found for native LFoo;.myfunc ()V

這意味着runtime嘗試匹配方法, 但沒匹配到, 通常的原因是:

• 庫沒有被加載. 檢查logcat關於加載庫的信息.

• 由於方法名或簽名的不匹配導致找不到方法. 這是最常見的原因:

◦ 對於懶得方法查找算法, 沒有對C++方法加上extern "C"的聲明和適當的可見性(JNIEXPORT).注意在4.0之前, JNIEXPORT宏是不正確的, 所以使用一個新的GCC和舊的jni.h文件來編譯是不能正常工作的.你可以使用arm-eabi-nm來看庫中的符號表.如果你看到錯位的符號(比如看到的是_Z15Java_Foo_myfuncP7_JNIEnvP7_jclass而不是Java_Foo_myfunc), 或者符號類型是小寫't'而不是大寫的'T', 那麼你需要調整聲明.

◦ 對於顯示註冊的方式, 最小的錯誤是方法簽名的錯誤. 確定你調用註冊方法時傳的簽名是正確的. 記得’B’是byte類型和’Z’是boolean類型.類名組件在簽名中是以’L’開始, ‘;’結束, 使用’/’來分隔包名和類名, 使用’  (Ljava/util/Map  Entry;).

使用javah來自動生成JNI頭文件會對某些問題有幫助.

FAQ: 爲什麼FindClass找不到我的類?


確定類名有正確的格式. JNI類名字由包名開始, 由’/’分隔, 例如 java/lang/String. 如果你查找一個數組類, 類名應該由中括號開始,裏面使用’L’和’;’包含的類名, 所以一個一維String數組定義爲[Ljava/lang/String;.

如果類名正確找到了, 你可能碰到一個加載類的問題. FindClass需要在一個類加載器裏進行類的搜索. 它會檢查調用棧, 這個棧會像這樣:

Foo.myfunc(Native Method)

Foo.main(Foo.java:10)

dalvik.system.NativeStart.main(Native Method)

最上面的方法是Foo.myfunc. FindClass找到類加載對象關聯的Foo類名並使用它.

這通常就是你所需要的. 但如果你在線程中使用FindClass可能會使你碰到麻煩, 這時調用棧應該是這樣的:

 dalvik.system.NativeStart.run(Native Method)

最上面的方法是NativeStart.run, 這個不是你程序中的代碼. 如果你在這個線程中使用FindClass, Java虛擬機將會在”system”類加載器而不是你自己應用對應的類加載器中進行查找, 所以這時查找一個你應用的類時是會失敗的.

針對這種情況這裏有幾個解決方案:

• 在JNI_OnLoad裏只做一次FindClass, 然後保存這些類的引用. 在JNI_OnLoad裏使用FindClass是能保證使用應用關聯的類加載器.

• 傳一個類的實例到方法裏, 通過聲明原生方法接收一個類作爲參數, 然後把Foo.class傳入.

• 保存類加載器的引用, 然後直接使用這個類加載器, 這需要作額外的工作.

FAQ: 我在原生代碼中怎樣分享原始數據?


你可能發現有這麼一個情形, 你有一個大的原始數據, 你需要在java和原生代碼中都訪問這個數據而不是把原始數據轉換成java的數據來處理. 常見的情況是一個處理圖片或聲音數據. 這裏有2種方式來實現.

你可以把數據存在byte[]中, 這個可以在java中有非常快的訪問速度. 在原生代碼中, 你不一定能保證不拷貝就能訪問它. 在某些實現中, GetByteArrayElements和GetPrimitiveArrayCritical將返回指向實際數據的指針, 但其他實現將會原生代碼層分配一塊內存來把數據拷貝進去.

還有一個方法是保存數據在直接byte緩存. 這個可以用java.nio.ByteBuffer.allocateDirect來創建, 或者用JNI的NewDirectByteBuffer方法. 不像通常的byte緩存, 這個存儲不在java的堆上分配空間, 並可以在原生代碼中直接使用訪問(通過GetDirectBufferAddress獲得地址). 由於依賴直接byte緩存的實現, 在有些系統上從java層訪問這個數據有可能會很慢.

可以依據下面2個因素來決定選擇哪個方案:

  1. 大部分的訪問是在Java層還是原生代碼層?

  2. 如果數據最終傳給系統調用, 那麼最終是以什麼格式傳入?(例如, 如果最終數據是以byte[]類型傳給方法, 那麼使用直接byte緩存來保存將不是個明智的選擇.)

如果沒有一個明顯的因素, 那麼使用直接byte緩存. 這個的支持是直接內置在JNI裏的, 而且將來還可能會被優化.

感謝博主的分享! 轉載自:http://blog.csdn.net/nicebooks/article/details/17925521

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章