高頻jni面試題及答案_第1頁
高頻jni面試題及答案_第2頁
高頻jni面試題及答案_第3頁
高頻jni面試題及答案_第4頁
高頻jni面試題及答案_第5頁
已閱讀5頁,還剩20頁未讀 繼續(xù)免費閱讀

付費下載

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認領

文檔簡介

高頻jni面試題及答案什么是JNI?其核心作用是什么?JNI(JavaNativeInterface)是Java平臺提供的編程接口規(guī)范,允許Java代碼與運行在虛擬機外部的本地代碼(主要是C/C++)進行交互。其核心作用體現(xiàn)在三方面:一是訪問底層系統(tǒng)功能(如操作系統(tǒng)API、硬件驅(qū)動),這些功能Java標準庫未直接提供;二是復用已有的C/C++代碼庫(如歷史遺留的算法庫、性能敏感的模塊),避免重復開發(fā);三是優(yōu)化特定場景的性能(某些計算密集型任務用C/C++實現(xiàn)比Java更高效)。Java聲明native方法到調(diào)用C/C++函數(shù)的完整流程是怎樣的?完整流程分為五步:第一步,在Java類中聲明native方法(如`publicnativeintnativeAdd(inta,intb);`);第二步,通過`javah`工具(JDK8及之前)或`javac-h`(JDK9及之后)提供對應的C/C++頭文件(如`com_example_Demo.h`),頭文件中會自動提供帶`Java_包名類名_方法名`前綴的函數(shù)聲明(如`JNIEXPORTjintJNICALLJava_com_example_Demo_nativeAdd(JNIEnv,jobject,jint,jint);`);第三步,編寫C/C++源文件實現(xiàn)頭文件中聲明的函數(shù),函數(shù)參數(shù)包含`JNIEnv`(指向JNI函數(shù)表的指針,用于操作Java對象)和`jobject`(調(diào)用該方法的Java對象實例,靜態(tài)方法則為`jclass`);第四步,使用C/C++編譯器(如GCC、Clang)將源文件編譯為動態(tài)庫(Windows為.dll,Linux為.so,macOS為.dylib);第五步,在Java代碼中通過`System.loadLibrary("庫名")`加載動態(tài)庫(庫名需去掉前綴和擴展名,如`libdemo.so`對應`loadLibrary("demo")`),加載成功后即可調(diào)用native方法。JNI中基本數(shù)據(jù)類型和Java數(shù)據(jù)類型如何對應?需要注意哪些轉(zhuǎn)換問題?JNI定義了與Java基本類型一一對應的本地類型:Java的`boolean`對應`jboolean`(無符號8位),`byte`對應`jbyte`(有符號8位),`char`對應`jchar`(無符號16位),`short`對應`jshort`(有符號16位),`int`對應`jint`(有符號32位),`long`對應`jlong`(有符號64位),`float`對應`jfloat`(32位),`double`對應`jdouble`(64位),`void`對應`void`。這些類型在傳遞時無需手動轉(zhuǎn)換,JNI會自動處理。但引用類型(如`String`、數(shù)組、自定義對象)需手動轉(zhuǎn)換。以`String`為例,Java的`String`在JNI中表示為`jstring`,若要在C/C++中使用其內(nèi)容,需通過`GetStringUTFChars`將`jstring`轉(zhuǎn)換為`constchar`(UTF-8編碼),使用完畢后必須調(diào)用`ReleaseStringUTFChars`釋放內(nèi)存,否則會導致內(nèi)存泄漏。若需修改字符串內(nèi)容,可使用`GetStringChars`獲取`constjchar`(UTF-16編碼),修改后通過`SetStringChars`寫回。處理Java數(shù)組時,基本類型數(shù)組(如`int[]`)對應`jintArray`,對象數(shù)組對應`jobjectArray`。對于基本類型數(shù)組,可通過`GetIntArrayElements`獲取指向數(shù)組元素的指針(可能返回副本,具體由`isCopy`參數(shù)決定),操作后用`ReleaseIntArrayElements`釋放;若只需讀取,可使用`GetIntArrayRegion`將數(shù)組內(nèi)容復制到本地數(shù)組,避免直接操作指針的風險。對象數(shù)組需通過`GetObjectArrayElement`逐個獲取元素,修改后用`SetObjectArrayElement`設置,需注意每個元素都是局部引用,若需跨函數(shù)使用需轉(zhuǎn)換為全局引用。JNI中的局部引用、全局引用、弱全局引用有何區(qū)別?如何正確管理?局部引用(LocalReference)是JNI函數(shù)中默認創(chuàng)建的引用(如通過`NewObject`、`FindClass`返回的引用),僅在當前線程的當前本地方法調(diào)用中有效。當本地方法返回或調(diào)用`DeleteLocalRef`顯式釋放后,局部引用失效。需注意:局部引用可能阻止GC回收其指向的Java對象,若在循環(huán)中大量創(chuàng)建(如處理大數(shù)組時逐個創(chuàng)建局部引用),可能導致內(nèi)存溢出,此時應使用`PushLocalFrame`和`PopLocalFrame`限制局部引用數(shù)量(JVM會自動清理超出幀大小的引用)。全局引用(GlobalReference)通過`NewGlobalRef`從局部引用創(chuàng)建,可在多個線程、多個本地方法調(diào)用中使用,直到調(diào)用`DeleteGlobalRef`顯式釋放。全局引用會強引用Java對象,阻止GC回收,因此需謹慎使用,避免內(nèi)存泄漏。弱全局引用(WeakGlobalReference)通過`NewWeakGlobalRef`創(chuàng)建,不會阻止GC回收目標對象。當目標對象被GC回收后,弱引用變?yōu)榭铡H跞忠眠m用于緩存場景(如需要緩存一個不常用的Java對象,避免長期占用內(nèi)存),使用前需通過`IsSameObject`檢查是否有效(若與`NULL`相同則已被回收)。管理引用的核心原則是:盡量使用局部引用,僅在需要跨方法/線程使用時創(chuàng)建全局引用;不再使用的引用立即釋放(尤其是循環(huán)中的局部引用);弱全局引用需配合有效性檢查使用,避免空指針訪問。JNI中如何處理Java異常?本地代碼拋出異常的流程是怎樣的?JNI中的異常處理需顯式操作,Java虛擬機不會自動將本地代碼的錯誤轉(zhuǎn)換為Java異常。處理流程分為兩種場景:1.Java代碼調(diào)用本地方法時拋出異常:本地代碼執(zhí)行過程中,若檢測到錯誤(如參數(shù)非法、資源獲取失?。柰ㄟ^`ThrowNew`手動拋出Java異常(如`IllegalArgumentException`)。例如:```cjclassexceptionCls=env->FindClass("java/lang/IllegalArgumentException");if(exceptionCls!=NULL){env->ThrowNew(exceptionCls,"Invalidparameter");}env->DeleteLocalRef(exceptionCls);//釋放局部引用```拋出異常后,本地方法應立即返回,Java層會捕獲該異常。2.本地代碼調(diào)用Java方法時可能拋出異常:例如調(diào)用`CallObjectMethod`時,Java方法可能拋出異常。此時本地代碼需通過`ExceptionCheck`檢查是否有未處理的異常(返回JNI_TRUE表示有異常)。若存在異常,本地代碼應:清理已分配的資源(如釋放局部引用、關閉文件句柄);決定是否處理異常(如通過`ExceptionClear`清除異常并執(zhí)行恢復邏輯)或向上傳播(直接返回,由Java層處理)。需注意:異常發(fā)生后,除了查詢異常信息(如`ExceptionDescribe`)和清除異常外,不能執(zhí)行其他JNI函數(shù),否則可能導致JVM崩潰。JNIEnv的作用是什么?能否跨線程使用?JNIEnv是一個指向JNI函數(shù)表的指針(本質(zhì)是`conststructJNINativeInterface`的指針),提供了操作Java對象、調(diào)用Java方法、管理引用等核心函數(shù)(如`NewStringUTF`、`GetMethodID`)。每個線程有獨立的JNIEnv實例,因為JVM為每個線程維護獨立的調(diào)用棧和局部引用表。因此,JNIEnv不能跨線程使用——若在A線程獲取的JNIEnv傳遞到B線程使用,會訪問到錯誤的局部引用表和線程狀態(tài),導致程序崩潰。若本地代碼需要在新線程中調(diào)用Java方法(如C/C++創(chuàng)建的線程),需通過`AttachCurrentThread`或`AttachCurrentThreadAsDaemon`將當前線程附加到JVM,獲取該線程的JNIEnv指針;線程結(jié)束前需通過`DetachCurrentThread`解除附加,避免JVM資源泄漏。如何緩存JNI中的方法ID(MethodID)和字段ID(FieldID)?需要注意哪些問題?方法ID和字段ID通過`GetMethodID`、`GetStaticMethodID`、`GetFieldID`、`GetStaticFieldID`獲取,這些函數(shù)需要傳入類對象(`jclass`)、方法/字段名、方法簽名(如`(II)I`表示參數(shù)為兩個int、返回值為int的方法)。由于`GetMethodID`等函數(shù)是耗時操作(需要查找類的方法表或字段表),頻繁調(diào)用會影響性能,因此需要緩存這些ID。緩存方法通常有兩種:1.靜態(tài)緩存:在本地方法首次調(diào)用時獲取ID,存儲為靜態(tài)變量。例如:```cstaticjmethodIDaddMethodID=NULL;JNIEXPORTjintJNICALLJava_com_example_Demo_nativeCallJavaMethod(JNIEnvenv,jobjectobj){if(addMethodID==NULL){jclasscls=env->GetObjectClass(obj);addMethodID=env->GetMethodID(cls,"add","(II)I");if(addMethodID==NULL){//處理方法不存在的異常return-1;}env->DeleteLocalRef(cls);}returnenv->CallIntMethod(obj,addMethodID,1,2);}```2.全局引用緩存:若需要在多個本地方法或線程中使用,可將`jclass`和方法ID存儲為全局引用。例如:```cstaticjclassdemoCls=NULL;staticjmethodIDdemoMethodID=NULL;JNIEXPORTjintJNICALLJNI_OnLoad(JavaVMvm,voidreserved){JNIEnvenv;if(vm->GetEnv((void)&env,JNI_VERSION_1_6)!=JNI_OK){returnJNI_ERR;}jclasslocalCls=env->FindClass("com/example/Demo");if(localCls==NULL){returnJNI_ERR;}demoCls=(jclass)env->NewGlobalRef(localCls);env->DeleteLocalRef(localCls);demoMethodID=env->GetMethodID(demoCls,"targetMethod","()V");if(demoMethodID==NULL){env->DeleteGlobalRef(demoCls);returnJNI_ERR;}returnJNI_VERSION_1_6;}```緩存時需注意:類可能被卸載(如自定義類加載器加載的類),導致緩存的`jclass`失效。此時需通過`IsSameObject(demoCls,NULL)`檢查類是否已被卸載,若失效需重新獲取并更新緩存。方法ID和字段ID在類加載后是固定的,只要類未被卸載,緩存的ID始終有效。但需確保方法/字段在類中確實存在,否則`GetMethodID`會返回NULL,導致后續(xù)調(diào)用崩潰。全局引用的`jclass`需在`JNI_OnUnload`中釋放(若JVM支持),避免內(nèi)存泄漏。JNI調(diào)用時如何避免內(nèi)存泄漏?常見的泄漏場景有哪些?避免內(nèi)存泄漏需嚴格管理引用和資源,常見場景及解決方法:1.未釋放局部引用:通過`NewObject`、`FindClass`、`GetObjectArrayElement`等函數(shù)創(chuàng)建的局部引用,若未在方法返回前釋放(或超出`PushLocalFrame`的作用域),可能導致JVM局部引用表溢出。解決方法:在不需要引用時立即調(diào)用`DeleteLocalRef`釋放,或使用`PushLocalFrame(100)`限制局部引用數(shù)量(JVM會在`PopLocalFrame`時自動清理)。2.未釋放全局/弱全局引用:通過`NewGlobalRef`、`NewWeakGlobalRef`創(chuàng)建的引用,若未調(diào)用`DeleteGlobalRef`、`DeleteWeakGlobalRef`釋放,會導致對象無法被GC回收(全局引用)或引用表資源浪費(弱全局引用)。解決方法:在確認不再使用時顯式釋放,通常在對應的清理函數(shù)或`JNI_OnUnload`中處理。3.未正確釋放字符串/數(shù)組的本地副本:通過`GetStringUTFChars`獲取的`char`、`GetIntArrayElements`獲取的`jint`,若未調(diào)用`ReleaseStringUTFChars`、`ReleaseIntArrayElements`釋放,可能導致內(nèi)存泄漏(JVM為這些操作分配的臨時緩沖區(qū)未被回收)。需注意:即使操作過程中發(fā)生異常,也需在異常處理塊中釋放這些資源。4.本地代碼中分配的內(nèi)存未釋放:如通過`malloc`分配的C/C++內(nèi)存,若未調(diào)用`free`釋放,會導致本地內(nèi)存泄漏(與Java堆無關,但會消耗系統(tǒng)資源)。解決方法:在本地代碼中嚴格配對使用`malloc`/`free`、`new`/`delete`。JNI與JNA的主要區(qū)別是什么?各自適用場景?JNI(JavaNativeInterface)和JNA(JavaNativeAccess)均用于Java與本地代碼交互,但實現(xiàn)方式和適用場景不同:實現(xiàn)方式:JNI需要編寫C/C++代碼并編譯為動態(tài)庫,Java代碼通過`native`方法調(diào)用;JNA基于JNI,通過Java反射和動態(tài)庫加載技術,直接調(diào)用本地函數(shù),無需編寫C/C++代碼(通過Java接口描述本地函數(shù))。性能:JNI直接調(diào)用本地函數(shù),性能略高(減少中間轉(zhuǎn)換開銷);JNA通過Java層反射和參數(shù)轉(zhuǎn)換,性能稍低(但對于大多數(shù)場景可忽略不計)。開發(fā)復雜度:JNI需掌握C/C++和JNI函數(shù),處理引用管理、類型轉(zhuǎn)換等細節(jié),開發(fā)周期較長;JNA只需定義Java接口(標注函數(shù)名、參數(shù)類型、調(diào)用約定),開發(fā)更簡單。適用場景:JNI適合需要高效交互、訪問復雜數(shù)據(jù)結(jié)構(gòu)(如指針、結(jié)構(gòu)體)或需要直接操作內(nèi)存的場景(如圖形處理、加密算法);JNA適合快速集成簡單的本地庫(如調(diào)用系統(tǒng)API獲取硬件信息)、避免維護C/C++代碼的場景。例如,調(diào)用Windows的`GetSystemTime`函數(shù),使用JNA只需定義:```javapublicinterfaceKernel32extendsLibrary{Kernel32INSTANCE=Native.load("kernel32",Kernel32.class);voidGetSystemTime(SYSTEMTIMElpSystemTime);}```而JNI需要編寫C代碼實現(xiàn)`native`方法,處理`SYSTEMTIME`結(jié)構(gòu)體的轉(zhuǎn)換。JNI中如何實現(xiàn)Java對象與C/C++對象的綁定?常見的綁定方式是在Java對象中存儲C/C++對象的指針(通過`long`類型字段),實現(xiàn)雙向引用。步驟如下:1.在Java類中定義`long`類型的字段(如`privatelongnativePtr;`),用于存儲C/C++對象的指針。2.本地方法中創(chuàng)建C/C++對象,將指針存入Java對象的`nativePtr`字段。例如:```cJNIEXPORTvoidJNICALLJava_com_example_NativeObject_init(JNIEnvenv,jobjectobj){MyNativeClassnativeObj=newMyNativeClass();//C++對象jfieldIDptrField=env->GetFieldID(env->GetObjectClass(obj),"nativePtr","J");env->SetLongField(obj,ptrField,(jlong)nativeObj);}```3.其他本地方法通過`GetLongField`獲取指針,轉(zhuǎn)換為C/C++對象指針后操作。例如:```cJNIEXPORTvoidJNICALLJava_com_example_NativeObject_doWork(JNIEnvenv,jobjectobj){jlongptr=env->GetLongField(obj,ptrField);//假設ptrField已緩存MyNativeClassnativeObj=(MyNativeClass)ptr;nativeObj->doWork();//調(diào)用C++方法}```4.Java對象銷毀時(如`finalize`方法),本地方法釋放C/C++對象。例如:```cJNIEXPORTvoidJNICALLJava_com_example_NativeObject_dispose(JNIEnvenv,jobjectobj){jlongptr=env->GetLongField(obj,ptrField);MyNativeClassnativeObj=(MyNativeClass)ptr;deletenativeObj;//釋放C++對象env->SetLongField(obj,ptrField,0);//清空指針}```需注意:確保`nativePtr`字段在Java對象生命周期內(nèi)有效,避免空指針訪問(如已調(diào)用`dispose`后再次調(diào)用`doWork`)。多線程環(huán)境下需對`nativePtr`的訪問加鎖,避免指針被釋放時其他線程仍在使用。若Java對象可能被GC回收,需在`finalize`方法中調(diào)用`dispose`,防止C/C++對象內(nèi)存泄漏。JNI調(diào)用時參數(shù)傳遞的性能瓶頸通常出現(xiàn)在哪些環(huán)節(jié)?如何優(yōu)化?性能瓶頸及優(yōu)化方法:1.頻繁的JNI方法調(diào)用:Java與本地代碼的切換涉及JVM棧與本地棧的切換、參數(shù)壓棧等操作,頻繁調(diào)用(如在循環(huán)中調(diào)用JNI方法)會導致性能下降。優(yōu)化方法:將多次小操作合并為一次大操作(如傳遞數(shù)組而非單個元素,批量處理數(shù)據(jù))。2.數(shù)據(jù)拷貝開銷:Java對象與本地數(shù)據(jù)的轉(zhuǎn)換(如`String`轉(zhuǎn)`char`、數(shù)組轉(zhuǎn)本地數(shù)組)可能涉及內(nèi)存拷貝。例如,`GetStringUTFChars`會創(chuàng)建UTF-8字符串的副本,`GetIntArrayElements`可能返回數(shù)組的副本(取決于JVM實現(xiàn))。優(yōu)化方法:使用`GetStringCritical`/`ReleaseStringCritical`(僅JDK1.2+支持),允許JVM直接返回原始字符串的指針(可能使JVM進入“臨界區(qū)”,期間不能調(diào)用其他JNI函數(shù)或觸發(fā)GC),減少拷貝。對于數(shù)組,優(yōu)先使用`GetArrayLength`獲取長度后,通過`GetPrimitiveArrayCritical`/`ReleasePrimitiveArrayCritical`直接操作原始數(shù)組內(nèi)存(需確保操作期間不調(diào)用其他JNI函數(shù))。3.方法ID/字段ID的重復查找:每次調(diào)用都通過`GetMethodID`查找方法ID會增加開銷。優(yōu)化方法:緩存方法ID和字段ID(如靜態(tài)變量或全局引用),僅在首次調(diào)用時查找。4.局部引用的過多創(chuàng)建:如在循環(huán)中多次調(diào)用`NewObject`創(chuàng)建局部引用,可能導致局部引用表膨脹,增加GC壓力。優(yōu)化方法:使用`PushLocalFrame`限制局部引用數(shù)量(如`PushLocalFrame(10)`),JVM會自動清理超出限制的引用;或復用已有的局部引用(如緩存一個對象實例)。5.跨線程調(diào)用的開銷:本地代碼創(chuàng)建的線程需要附加到JVM(`AttachCurrentThread`),該操作有一定開銷。優(yōu)化方法:復用線程池,減少線程的創(chuàng)建和附加次數(shù);若無需頻繁調(diào)用Java方法,可在首次附加后長期保留線程。JNI中如何處理Java對象的繼承關系?例如,本地代碼如何判斷一個`jobject`是否是某個類的實例?可通過`IsInstanceOf`函數(shù)判斷`jobject`是否是某個類的實例。步驟如下:1.獲取目標類的`jclass`(通過`FindClass`或緩存的全局引用)。2.調(diào)用`env->IsInstanceOf(obj,cls)`,返回JNI_TRUE表示`obj`是`cls`或其子類的實例。例如,判斷`jobject`是否是`java.util.List`的實例:```cjclasslistCls=env->FindClass("java/util/List");if(listCls==NULL){/處理類未找到/}jbooleanisList=env->IsInstanceOf(obj,listCls);env->DeleteLocalRef(listCls);if(isList==JNI_TRUE){//是List實例}```若需要判斷是否是精確類型(而非子類),可通過`GetObjectClass`獲取對象的類,再用`IsSameObject`比較:```cjclassobjCls=env->GetObjectClass(obj);jbooleanisExact=env->IsSameObject(objCls,targetCls);env->DeleteLocalRef(objCls);```JNI中如何調(diào)用Java的靜態(tài)方法和構(gòu)造方法?調(diào)用靜態(tài)方法需通過`CallStaticXXXMethod`系列函數(shù)(如`CallStaticIntMethod`),步驟:1.獲取目標類的`jclass`(`jclasscls=env->FindClass("com/example/Cls");`)。2.獲取靜態(tài)方法ID(`jmethodIDmid=env->GetStaticMethodID(cls,"methodName","(II)I");`)。3.調(diào)用靜態(tài)方法(`jintresult=env->CallStaticIntMethod(cls,mid,1,2);`)。調(diào)用構(gòu)造方法需通過`NewObject`函數(shù),構(gòu)造方法的方法名固定為`<init>`,返回類型為`void`(簽名為`V`)。步驟:1.獲取目標類的`jclass`。2.獲取構(gòu)造方法ID(`jmethodIDctor=env->GetMethodID(cls,"<init>","(Ljava/lang/String;)V");`)。3.創(chuàng)建對象(`jobjectobj=env->NewObject(cls,ctor,env->NewStringUTF("test"));`)。需注意:構(gòu)造方法調(diào)用失?。ㄈ鐓?shù)錯誤)會拋出`InstantiationException`或`IllegalAccessException`,本地代碼需通過`ExceptionCheck`檢測并處理。JNI中如何訪問Java對象的字段(實例字段和靜態(tài)字段)?訪問實例字段:1.獲取對象的類(`jclasscls=env->GetObjectClass(obj);`)。2.獲取字段ID(`jfieldIDfid=env->GetFieldID(cls,"fieldName","I");`,第三個參數(shù)為字段的類型簽名,如`I`表示int)。3.獲取字段值(`jintvalue=env->GetIntField(obj,fid);`)或設置字段值(`env->SetIntField(obj,fid,10);`)。訪問靜態(tài)字段:1.獲取目標類的`jclass`(`jclasscls=env->FindClass("com/example/Cls");`)。2.獲取靜態(tài)字段ID(`jfieldIDfid=env->GetStaticFieldID(cls,"staticField","D");`,`D`表示double)。3.獲取靜態(tài)字段值(`jdoublevalue=env->GetStaticDoubleField(cls,

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責。
  • 6. 下載文件中如有侵權(quán)或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

最新文檔

評論

0/150

提交評論