字段和方法

現(xiàn)在林束,你知道了如何通過JNI來訪問JVM中的基本類型數(shù)據(jù)和字符串惹恃、數(shù)組這樣的引用類型數(shù)據(jù),下一步就是學(xué)習(xí)怎么樣和JVM中任意對象的字段和方法進(jìn)行交互恃锉。比如從本地代碼中調(diào)用JAVA中的方法,也就是通常說的來自本地方法中的callbacks(回調(diào))呕臂。
我們從進(jìn)行字段訪問和方法回調(diào)時需要的JNI函數(shù)開始講解破托。本章的稍后部分我們會討論怎么樣通過一些cache(緩存)技術(shù)來優(yōu)化這些操作。在最后歧蒋,我們還會討論從本地代碼中訪問字段和回調(diào)方法時的效率問題土砂。

4.1 訪問字段

JAVA支持兩種field(字段),每一個對象的實例都有一個對象字段的復(fù)制谜洽;所有的對象共享一個類的靜態(tài)字段萝映。本地方法使用JNI提供的函數(shù)可以獲取和修改這兩種字段。先看一個從本地代碼中訪問對象字段的例子:
class InstanceFieldAccess {
private String s;

 private native void accessField();
 public static void main(String args[]) {
     InstanceFieldAccess c = new InstanceFieldAccess();
     c.s = "abc";
     c.accessField();
     System.out.println("In Java:");
     System.out.println("  c.s = \"" + c.s + "\"");
 }
 static {
     System.loadLibrary("InstanceFieldAccess");
 }

}
InstanceFieldAccess這個類定義了一個對象字段s阐虚。main方法創(chuàng)建了一個對象并設(shè)置s的值序臂,然后調(diào)用本地方法InstanceFieldAccess.accessField在本地代碼中打印s的值,并把它修改為一個新值实束。本地方法返回后奥秆,JAVA中把這個值再打印一次,可以看出來咸灿,字段s的值已經(jīng)被改變了构订。下面是本地方法的實現(xiàn):
JNIEXPORT void JNICALL
Java_InstanceFieldAccess_accessField(JNIEnv env, jobject obj)
{
jfieldID fid; /
store the field ID */
jstring jstr;
const char *str;

 /* Get a reference to obj's class */
 jclass cls = (*env)->GetObjectClass(env, obj);

 printf("In C:\n");

 /* Look for the instance field s in cls */
 fid = (*env)->GetFieldID(env, cls, "s",
                          "Ljava/lang/String;");
 if (fid == NULL) {
     return; /* failed to find the field */
 }




 /* Read the instance field s */
 jstr = (*env)->GetObjectField(env, obj, fid);
 str = (*env)->GetStringUTFChars(env, jstr, NULL);
 if (str == NULL) {
     return; /* out of memory */
 }
 printf("  c.s = \"%s\"\n", str);
 (*env)->ReleaseStringUTFChars(env, jstr, str);

 /* Create a new string and overwrite the instance field */
 jstr = (*env)->NewStringUTF(env, "123");
 if (jstr == NULL) {
     return; /* out of memory */
 }
 (*env)->SetObjectField(env, obj, fid, jstr);

}
運行程序,得到輸出為:
In C:
c.s = "abc"
In Java:
c.s = "123"

4.1.1 訪問一個對象字段的流程

為了訪問一個對象的實例字段避矢,本地方法需要做兩步:
首先悼瘾,通過在類引用上調(diào)用GetFieldID獲取field ID(字段ID)、字段名字和字段描述符:
Fid=(env)->GetFieldID(env,cls,”s”,”Ljava/lang/String;”);
上例中的代碼通過在對象引用obj上調(diào)用GetObjectClass獲取到類引用审胸。一旦獲取到字段ID亥宿,你就可以把對象和字段ID作為參數(shù)來訪問字段:
Jstr=(
env)->GetObjectField(env,obj,fid);
因為字符串和數(shù)組是特殊的對象,所以我們使用GetObjectField來訪問字符串類型的實例字段歹嘹。除了Get/SetObjectField箩绍,JNI還支持其它如GetIntField、SetFloatField等用來訪問基本類型字段的函數(shù)尺上。

4.1.2 字段描述符

在上一節(jié)我們使用過一個特殊的C字符串“Ljava/lang/String”來代表一個JVM中的字段類型材蛛。這個字符串被稱為JNI field descriptor(字段描述符)圆到。
字符串的內(nèi)容由字段被聲明的類型決定。例如卑吭,使用“I”來表示一個int類型的字段芽淡,“F”來表示一個float類型的字段,“D”來表示一個double類型的字段豆赏,“Z”來表示一個boolean類型的字段等等挣菲。
像java.lang.String這樣的引用類型的描述符都是以L開頭,后面跟著一個JNI類描述符掷邦,以分號結(jié)尾白胀。一個JAVA類的全名中的包名分隔符“.”被轉(zhuǎn)化成“/”。因此抚岗,對于一個字段類型的字段來說或杠,它的描述符是“Ljava/lang/String”。
數(shù)組的描述符中包含“]”字符宣蔚,后面會跟著數(shù)組類型的描述符向抢,如“[I”是int[]類型的字段的描述符。12.3.3詳細(xì)介紹了各種類型的字段描述以及他們代表的JAVA類型胚委。
你可以使用javap工具來生成字段描述符挟鸠。

4.1.3 訪問靜態(tài)字段

訪問靜態(tài)字段和訪問實例字段相似,看下面這個InstanceFieldAccess例子的變形:
class StaticFielcdAccess {
private static int si;

 private native void accessField();
 public static void main(String args[]) {
     StaticFieldAccess c = new StaticFieldAccess();
     StaticFieldAccess.si = 100;
     c.accessField();
     System.out.println("In Java:");
     System.out.println("  StaticFieldAccess.si = " + si);
 }
 static {
     System.loadLibrary("StaticFieldAccess");
 }

}
StaticFieldAccess這個類包含一個靜態(tài)字段si亩冬,main方法創(chuàng)建了一個對象艘希,初始化靜態(tài)字段,然后調(diào)用本地方法StaticFieldAccess.accessField在本地代碼中打印靜態(tài)字段中的值鉴未,然后設(shè)置新的值枢冤,為了演示這個值確實被改變了,在本地方法返回后铜秆,JAVA中再次這個靜態(tài)字段的值淹真。
下面是本地方法StaticFieldAccess.accessField的實現(xiàn):
JNIEXPORT void JNICALL
Java_StaticFieldAccess_accessField(JNIEnv env, jobject obj)
{
jfieldID fid; /
store the field ID */
jint si;

 /* Get a reference to obj's class */
 jclass cls = (*env)->GetObjectClass(env, obj);

 printf("In C:\n");

 /* Look for the static field si in cls */
 fid = (*env)->GetStaticFieldID(env, cls, "si", "I");
 if (fid == NULL) {
     return; /* field not found */
 }
 /* Access the static field si */
 si = (*env)->GetStaticIntField(env, cls, fid);
 printf("  StaticFieldAccess.si = %d\n", si);
 (*env)->SetStaticIntField(env, cls, fid, 200);

}
運行程序可得到輸出結(jié)果:
In C:
StaticFieldAccess.si = 100
In Java:
StaticFieldAccess.si = 200
訪問靜態(tài)字段和對象實例字段的不同點:
1、 訪問靜態(tài)字段使用GetStaticFieldID连茧,而訪問對象的實例字段使用GetFieldID核蘸,但是,這兩個方法都有相同的返回值類型:jfieldID啸驯。

4.2 調(diào)用方法

JAVA中有幾種不同類型的方法客扎,實例方法必須在一個類的某個對象實例上面調(diào)用。而靜態(tài)方法可以在任何一個對象實例上調(diào)用罚斗。對于構(gòu)建方法的調(diào)用我們推遲到下一節(jié)徙鱼。
JNI支持一系列完整的函數(shù)讓你可以在本地代碼中回調(diào)JAVA方法,下面例子演示了如何從本地代碼中調(diào)用一個JAVA中的實例方法:
class InstanceMethodCall {
private native void nativeMethod();
private void callback() {
System.out.println("In Java");
}
public static void main(String args[]) {
InstanceMethodCall c = new InstanceMethodCall();
c.nativeMethod();
}
static {
System.loadLibrary("InstanceMethodCall");
}
}
下面的是本地方法的實現(xiàn):
JNIEXPORT void JNICALL
Java_InstanceMethodCall_nativeMethod(JNIEnv env, jobject obj)
{
jclass cls = (
env)->GetObjectClass(env, obj);
jmethodID mid =
(env)->GetMethodID(env, cls, "callback", "()V");
if (mid == NULL) {
return; /
method not found /
}
printf("In C\n");
(
env)->CallVoidMethod(env, obj, mid);
}
運行程序,得到如下輸出:
In C
In Java

4.2.1 調(diào)用實例方法

本地方法Java_InstanceMethodCall_nativeMethod的實現(xiàn)演示了在本地代碼中調(diào)用JAVA方法的兩步:
1袱吆、 本地方法首先調(diào)用JNI函數(shù)GetMethodID厌衙。這個函數(shù)在指定的類中尋找相應(yīng)的方法。這個尋找過程是基于方法描述符的绞绒。如果方法不存在婶希,GetMethodID返回NULL。這時蓬衡,立即從本地方法中返回喻杈,并引發(fā)一個NoSuchMethodError錯誤。
2狰晚、 本地方法通過調(diào)用CallVoidMethod來調(diào)用返回值為void的實例方法筒饰。
除了CallVoidMethod這個函數(shù)以外,JNI也支持對返回值為其它類型的方法的調(diào)用家肯。如果你調(diào)用的方法返回值類型為int龄砰,你的本地方法會使用CallIntMethod。類似地讨衣,你可以調(diào)用CallObjectMethod來調(diào)用返回值為java.lang.String、數(shù)組等對象類型的方法式镐。
你也可以使用Call<Type>Method系列的函數(shù)來調(diào)用接口方法反镇。你必須從接口類型中獲取方法ID,下面的代碼演示了如何在java.lang.Thread實例上面調(diào)用Runnable.run方法:
jobject thd = ...; /* a java.lang.Thread instance /
jmethodID mid;
jclass runnableIntf =
(
env)->FindClass(env, "java/lang/Runnable");
if (runnableIntf == NULL) {
... /* error handling /
}
mid = (
env)->GetMethodID(env, runnableIntf, "run", "()V");
if (mid == NULL) {
... /* error handling /
}
(
env)->CallVoidMethod(env, thd, mid);
... /* check for possible exceptions */
在3.3.5中娘汞,我們使用FindClass來獲取一個類的引用歹茶,在這里,我們可以學(xué)到如何獲取一個接口的引用你弦。

4.2.2 生成方法描述符

JNI中描述字段使用字段描述符惊豺,描述方法同樣有方法描述符。一個方法描述符包含參數(shù)類型和返回值類型禽作。參數(shù)類型出現(xiàn)在前面尸昧,并由一對圓括號將它們括起來,參數(shù)類型按它們在方法聲明中出現(xiàn)的順序被列出來旷偿,并且多個參數(shù)類型之間沒有分隔符烹俗。如果一個方法沒有參數(shù),被表示為一對空圓括號萍程。方法的返回值類型緊跟參數(shù)類型的右括號后面幢妄。
例如,“(I)V”表示這個方法的一個參數(shù)類型為int茫负,并且有一個void類回值蕉鸳。“()D”表示這個方法沒有參數(shù)忍法,返回值類型為double潮尝。
方法描述符中可能會包含類描述符(12.3.2)榕吼,如方法native private String getLine(String);的描述符為:“(Ljava/lang/String;)Ljava/lang/String;”
數(shù)組類型的描述符以“[”開頭,后面跟著數(shù)組元素類型的描述符衍锚。如友题,public static void main(String[] args);的描述符是:"([Ljava/lang/String;)V"
12.3.4詳細(xì)描述了怎么樣生成一個JNI方法描述符。同樣戴质,你可以使用javap工具來打印出JNI方法描述符度宦。
4.2.3 調(diào)用靜態(tài)方法
前一個例子演示了一個本地方法怎樣調(diào)用實例方法。類似地告匠,本地方法中同樣可以調(diào)用靜態(tài)方法:
1戈抄、 通過GetStaticMethodID獲取方法ID。對應(yīng)于調(diào)用實例方法時的GetMethodID后专。
2划鸽、 傳入類、方法ID戚哎、參數(shù)裸诽,并調(diào)用提供靜態(tài)方法調(diào)用功能的JNI系列函數(shù)中的一個,如:CallStaticVoidMethod型凳,CallStaticBooleanMethod等丈冬。
調(diào)用靜態(tài)方法和調(diào)用實例方法的JNI函數(shù)有一個很大的不同,前者第二個參數(shù)是類引用甘畅,后者是對象實例的引用埂蕊。
在JAVA訪問一個靜態(tài)方法可以通過類,也可以通過對象實例疏唾。而JNI的規(guī)定是蓄氧,在本地代碼中回調(diào)JAVA中的靜態(tài)方法時,必須指定一個類引用才行槐脏。下面的例子演示了這個用法:
class StaticMethodCall {
private native void nativeMethod();
private static void callback() {
System.out.println("In Java");
}
public static void main(String args[]) {
StaticMethodCall c = new StaticMethodCall();
c.nativeMethod();
}
static {
System.loadLibrary("StaticMethodCall");
}
}
下面是本地方法的實現(xiàn):
JNIEXPORT void JNICALL
Java_StaticMethodCall_nativeMethod(JNIEnv env, jobject obj)
{
jclass cls = (
env)->GetObjectClass(env, obj);
jmethodID mid =
(env)->GetStaticMethodID(env, cls, "callback", "()V");
if (mid == NULL) {
return; /
method not found /
}
printf("In C\n");
(
env)->CallStaticVoidMethod(env, cls, mid);
}
當(dāng)調(diào)用CallStaticVoidMethod時喉童,確保你傳入的是類引用cls而不是對象引用obj。運行程序准给,輸出為:
In C
In Java
4.2.4 調(diào)用父類的實例方法
如果一個方法被定義在父類中泄朴,在子類中被覆蓋,你也可以調(diào)用這個實例方法露氮。JNI提供了一系列完成這些功能的函數(shù):CallNonvirtual<Type>Method祖灰。為了調(diào)用一個定義在父類中的實例方法,你必須遵守下面的步驟:
1畔规、 使用GetMethodID從一個指向父類的引用當(dāng)中獲取方法ID局扶。
2、 傳入對象、父類三妈、方法ID和參數(shù)畜埋,并調(diào)用CallNonvirtualVoidMethod、CallNonvirtualBooleanMethod等一系列函數(shù)中的一個畴蒲。
這種調(diào)用父類實例方法的情況其實很少遇到悠鞍,通常在JAVA中可以很簡單地做到:super.f();
CallNonvirtualVoidMethod也可以被用來調(diào)用父類的構(gòu)造函數(shù)。這個在下節(jié)就會講到模燥。
4.3 調(diào)用構(gòu)造函數(shù)
JNI中咖祭,構(gòu)造函數(shù)可以和實例方法一樣被調(diào)用隅津,調(diào)用方式也相似异赫。傳入“<init>”作為方法名蹭睡,“V”作為返回類型织阳。你可以通過向JNI函數(shù)NewObject傳入方法來調(diào)用構(gòu)造函數(shù)。下面的代碼實現(xiàn)了與JNI函數(shù)NewString相同的功能:把存儲在C緩沖區(qū)內(nèi)的Unicode編碼的字符序列田巴,創(chuàng)建成一個java.lang.String對象:
jstring
MyNewString(JNIEnv *env, jchar *chars, jint len)
{
jclass stringClass;
jmethodID cid;
jcharArray elemArr;
jstring result;

 stringClass = (*env)->FindClass(env, "java/lang/String");
 if (stringClass == NULL) {
     return NULL; /* exception thrown */
 }

/* Get the method ID for the String(char[]) constructor /
cid = (
env)->GetMethodID(env, stringClass,
"<init>", "([C)V");
if (cid == NULL) {
return NULL; /* exception thrown */
}

 /* Create a char[] that holds the string characters */
 elemArr = (*env)->NewCharArray(env, len);
 if (elemArr == NULL) {
     return NULL; /* exception thrown */
 }
 (*env)->SetCharArrayRegion(env, elemArr, 0, len, chars);

 /* Construct a java.lang.String object */
 result = (*env)->NewObject(env, stringClass, cid, elemArr);

 /* Free local references */
 (*env)->DeleteLocalRef(env, elemArr);
 (*env)->DeleteLocalRef(env, stringClass);
 return result;

}
上面這個本地方法有些復(fù)雜超歌,需要詳細(xì)解釋一下专普。首先补胚,F(xiàn)indClass返回一個java.lang.String類的引用码耐,接著,GetMethodID返回構(gòu)造函數(shù)String(char[] chars)的方法ID溶其。我們調(diào)用NewCharArray分配一個字符數(shù)組來保存字符串元素伐坏。JNI函數(shù)NewObject調(diào)用方法ID所標(biāo)識的構(gòu)造函數(shù)。NewObject函數(shù)需要的參數(shù)有:類的引用握联、構(gòu)造方法的ID、構(gòu)造方法需要的參數(shù)每瞒。
DeleteLocalRef允許VM釋放被局部引用elemArr和stringClass引用的資源金闽。5.2.1中詳細(xì)描述了調(diào)用DeleteLocalRef的時機(jī)和原因。
這個例子引出了一個問題剿骨,既然我們可以利用JNI函數(shù)來實現(xiàn)相同的功能代芜,為什么JNI還需要NewString這樣的內(nèi)置函數(shù)?原因是浓利,內(nèi)置函數(shù)的效率遠(yuǎn)高于在本地代碼里面調(diào)用構(gòu)造函數(shù)的API挤庇。而字符串又是最常用到的對象類型,因此需要在JNI中給予特殊的支持贷掖。
你也可以做到通過CallNonvirtualVoidMethod函數(shù)來調(diào)用構(gòu)造函數(shù)嫡秕。這種情況下,本地代碼必須首先通過調(diào)用AllocObject函數(shù)創(chuàng)建一個未初始化的對象苹威。上面例子中的result = (env)->NewObject(env, stringClass, cid, elemArr);可以被如下代碼替換:
result = (
env)->AllocObject(env, stringClass);
if (result) {
(env)->CallNonvirtualVoidMethod(env, result, stringClass,
cid, elemArr);
/
we need to check for possible exceptions /
if ((
env)->ExceptionCheck(env)) {
(*env)->DeleteLocalRef(env, result);
result = NULL;
}
}
AllocObject創(chuàng)建了一個未初始化的對象昆咽,使用時一定要非常小心,確保一個對象上面,構(gòu)造函數(shù)最多被調(diào)用一次掷酗。本地代碼不應(yīng)該在一個對象上面調(diào)用多次構(gòu)造函數(shù)调违。有時,你可能會發(fā)現(xiàn)創(chuàng)建一個未初始化的對象然后一段時間以后再調(diào)用構(gòu)造函數(shù)的方式是很有用的泻轰。盡管如此技肩,大部分情況下,你應(yīng)該使用NewObject浮声,盡量避免使用容易出錯的AllocObject/CallNonvirtualVoidMethod方法虚婿。

4.4 緩存字段ID和方法ID

獲取字段ID和方法ID時,需要用字段阿蝶、方法的名字和描述符進(jìn)行一個檢索雳锋。檢索過程相對比較費時,因此本節(jié)討論用緩存技術(shù)來減少這個過程帶來的消耗羡洁。緩存字段ID和方法ID的方法主要有兩種玷过。兩種區(qū)別主要在于緩存發(fā)生的時刻,是在字段ID和方法ID被使用的時候筑煮,還是定義字段和方法的類靜態(tài)初始化的時候辛蚊。

4.4.1 使用時緩存

字段ID和方法ID可以在字段的值被訪問或者方法被回調(diào)的時候緩存起來。下面的代碼中把字段ID存儲在靜態(tài)變量當(dāng)中真仲,這樣當(dāng)本地方法被重復(fù)調(diào)用時袋马,不必重新搜索字段ID:
JNIEXPORT void JNICALL
Java_InstanceFieldAccess_accessField(JNIEnv env, jobject obj)
{
static jfieldID fid_s = NULL; /
cached field ID for s */

 jclass cls = (*env)->GetObjectClass(env, obj);
 jstring jstr;
 const char *str;

 if (fid_s == NULL) {
     fid_s = (*env)->GetFieldID(env, cls, "s", 
                                "Ljava/lang/String;");
     if (fid_s == NULL) {
         return; /* exception already thrown */
     }
 }

 printf("In C:\n");

 jstr = (*env)->GetObjectField(env, obj, fid_s);
 str = (*env)->GetStringUTFChars(env, jstr, NULL);
 if (str == NULL) {
     return; /* out of memory */
 }
 printf("  c.s = \"%s\"\n", str);
 (*env)->ReleaseStringUTFChars(env, jstr, str);

 jstr = (*env)->NewStringUTF(env, "123");
 if (jstr == NULL) {
     return; /* out of memory */
 }
 (*env)->SetObjectField(env, obj, fid_s, jstr);

}
由于多個線程可能同時訪問這個本地方法,上面方法中的代碼很可能會導(dǎo)致混亂秸应,其實沒事虑凛,多個線程計算的ID其實是相同的。
同樣的思想软啼,我們也可以緩存java.lang.String的構(gòu)造方法的ID:
jstring
MyNewString(JNIEnv *env, jchar *chars, jint len)
{
jclass stringClass;
jcharArray elemArr;
static jmethodID cid = NULL;
jstring result;

 stringClass = (*env)->FindClass(env, "java/lang/String");
 if (stringClass == NULL) {
     return NULL; /* exception thrown */
 }

 /* Note that cid is a static variable */
 if (cid == NULL) {
     /* Get the method ID for the String constructor */
     cid = (*env)->GetMethodID(env, stringClass,
                               "<init>", "([C)V");
     if (cid == NULL) {
         return NULL; /* exception thrown */
     }
 }

 /* Create a char[] that holds the string characters */
 elemArr = (*env)->NewCharArray(env, len);
 if (elemArr == NULL) {
     return NULL; /* exception thrown */
 }
 (*env)->SetCharArrayRegion(env, elemArr, 0, len, chars);

 /* Construct a java.lang.String object */
 result = (*env)->NewObject(env, stringClass, cid, elemArr);

 /* Free local references */
 (*env)->DeleteLocalRef(env, elemArr);
 (*env)->DeleteLocalRef(env, stringClass);
 return result;

}
當(dāng)MyNewString方法第一次被調(diào)用時桑谍,我們計算java.lang.String的構(gòu)造方法的ID,并存儲在靜態(tài)變量cid中祸挪。
4.4.2 類的靜態(tài)初始化過程中緩存字段和方法ID
我們在使用時緩存字段和方法的ID的話锣披,每次本地方法被調(diào)用時都要檢查ID是否已經(jīng)被緩存。許多情況下贿条,在字段ID和方法ID被使用前就初始化是很方便的雹仿。VM在調(diào)用一個類的方法和字段之前,都會執(zhí)行類的靜態(tài)初始化過程整以,所以在靜態(tài)初始化該類的過程中計算并緩存字段ID和方法ID是個不錯的選擇胧辽。
例如,為了緩存InstanceMethodCall.callback的方法ID悄蕾,我們引入了一個新的本地方法initIDs票顾,這個方法在InstanceMethodCall的靜態(tài)初始化過程中被調(diào)用础浮。代碼如下:
class InstanceMethodCall {
private static native void initIDs();
private native void nativeMethod();
private void callback() {
System.out.println("In Java");
}
public static void main(String args[]) {
InstanceMethodCall c = new InstanceMethodCall();
c.nativeMethod();
}
static {
System.loadLibrary("InstanceMethodCall");
initIDs();
}
}
與4.2節(jié)中的代碼相比,上面這段代碼多了兩行奠骄,initIDs方法簡單地計算并緩存方法ID:
jmethodID MID_InstanceMethodCall_callback;

JNIEXPORT void JNICALL
Java_InstanceMethodCall_initIDs(JNIEnv env, jclass cls)
{
MID_InstanceMethodCall_callback =
(
env)->GetMethodID(env, cls, "callback", "()V");
}
VM進(jìn)行靜態(tài)初始化時在調(diào)用任何方法前調(diào)用initIDs豆同,這樣方法ID就被緩存了全局變量中,本地方法的實現(xiàn)就不必再進(jìn)行ID計算:
JNIEXPORT void JNICALL
Java_InstanceMethodCall_nativeMethod(JNIEnv env, jobject obj)
{
printf("In C\n");
(
env)->CallVoidMethod(env, obj,
MID_InstanceMethodCall_callback);
}
4.4.3 兩種緩存ID的方式之間的對比
如果JNI程序員不能控制方法和字段所在的類的源碼的話含鳞,在使用時緩存是個合理的方案影锈。例如在MyNewString當(dāng)中,我們不能在String類中插入一個initIDs方法蝉绷。
比起靜態(tài)初始時緩存來說鸭廷,使用時緩存有一些缺點:
1、 使用時緩存的話熔吗,每次使用時都要檢查一下辆床。
2、 方法ID和字段ID在類被unload時就會失效桅狠,如果你在使用時緩存ID讼载,你必須確保只要本地代碼依賴于這個ID的值,那么這個類不被會unload(下一章演示了如何通過使用JNI函數(shù)創(chuàng)建一個類引用來防止類被unload)中跌。另一方面咨堤,如果緩存發(fā)生在靜態(tài)初始化時,當(dāng)類被unload和reload時漩符,ID會被重新計算一喘。
因此,盡可能在靜態(tài)初始化時緩存字段ID和方法ID嗜暴。
4.5 JNI操作JAVA中的字段和方法時的效率
學(xué)完了如何緩存ID來提高效率后凸克,你可能會對使用JNI訪問java字段和方法的效率不太明白,native/java比起java/native和java/java來的話闷沥,效率如何呢触徐?
當(dāng)然,這取決于VM的實現(xiàn)狐赡。我們不能給出在大范圍的VM上通用的數(shù)據(jù),但我們可以通過分析本地方法回調(diào)java方法和JNI操作字段以及方法的過程來給出一個大致的概念疟丙。
我們從比較java/native和java/java的效率開始颖侄。java/native調(diào)用比java/java要慢,主要有以下幾個原因:
1享郊、 java/native比起JVM內(nèi)部的java/java來說有一個調(diào)用轉(zhuǎn)換過程览祖,在把控制權(quán)和入口切換給本地方法之前,VM必須做一些額外的操作來創(chuàng)建參數(shù)和棧幀炊琉。
2展蒂、 對VM來說又活,對方法調(diào)用進(jìn)行內(nèi)聯(lián)比較容易,而內(nèi)聯(lián)java/native方法要難得多锰悼。
據(jù)我們的估計柳骄,VM進(jìn)行java/native調(diào)用時的消耗是java/java的2~3倍。當(dāng)然VM可以進(jìn)行一些調(diào)整箕般,使用java/native的消耗接近或者等于java/java的消耗耐薯。
技術(shù)上來講,native/java調(diào)用和java/native是相似的丝里。但實際上native/java調(diào)用很少見曲初,VM通常不會優(yōu)化native/java這種回調(diào)方式。多數(shù)VM中杯聚,native/java調(diào)用的消耗可以達(dá)到j(luò)ava/java調(diào)用的10倍臼婆。
使用JNI訪問字段的花費取決于通過JNIEnv進(jìn)行調(diào)用的消耗。以廢棄一個對象引用來說幌绍,本地代碼必須依賴于特定的JNI函數(shù)才能做到颁褂,而這個依賴是必須的,它把本地代碼和VM中對象的內(nèi)部形式很好地隔離開纷捞。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末痢虹,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子主儡,更是在濱河造成了極大的恐慌奖唯,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件糜值,死亡現(xiàn)場離奇詭異丰捷,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)寂汇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進(jìn)店門病往,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人骄瓣,你說我怎么就攤上這事停巷。” “怎么了榕栏?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵畔勤,是天一觀的道長。 經(jīng)常有香客問我扒磁,道長庆揪,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任妨托,我火速辦了婚禮缸榛,結(jié)果婚禮上吝羞,老公的妹妹穿的比我還像新娘。我一直安慰自己内颗,他們只是感情好钧排,可當(dāng)我...
    茶點故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著起暮,像睡著了一般卖氨。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上负懦,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天筒捺,我揣著相機(jī)與錄音,去河邊找鬼纸厉。 笑死系吭,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的颗品。 我是一名探鬼主播肯尺,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼躯枢!你這毒婦竟也來了则吟?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤锄蹂,失蹤者是張志新(化名)和其女友劉穎氓仲,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體得糜,經(jīng)...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡敬扛,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了朝抖。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片啥箭。...
    茶點故事閱讀 40,664評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖治宣,靈堂內(nèi)的尸體忽然破棺而出急侥,到底是詐尸還是另有隱情,我是刑警寧澤侮邀,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布缆巧,位于F島的核電站,受9級特大地震影響豌拙,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜题暖,卻給世界環(huán)境...
    茶點故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一按傅、第九天 我趴在偏房一處隱蔽的房頂上張望捉超。 院中可真熱鬧,春花似錦唯绍、人聲如沸拼岳。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽惜纸。三九已至,卻和暖如春绝骚,著一層夾襖步出監(jiān)牢的瞬間耐版,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工压汪, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留粪牲,地道東北人。 一個月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓止剖,卻偏偏與公主長得像腺阳,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子穿香,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,675評論 2 359

推薦閱讀更多精彩內(nèi)容

  • 花了幾天時間研究了下JNI亭引,基本上知道如何使用了。照我的觀點JNI還是不難的皮获,難得只是我們一份嘗試的心焙蚓。 學(xué)習(xí)過程...
    皇小弟閱讀 1,621評論 0 1
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)魔市,斷路器主届,智...
    卡卡羅2017閱讀 134,704評論 18 139
  • 本章是JNI設(shè)計思想的一個概述,在講的過程中待德,如果有必要的話君丁,還會對底層實現(xiàn)技術(shù)的原理做說明。本章也可以看作是JN...
    738bc070cd74閱讀 459評論 0 0
  • 注:原文地址 1. JNI 概念 1.1 概念 JNI 全稱 Java Native Interface将宪,Java...
    cfanr閱讀 57,786評論 9 132
  • 我擅于隱藏绘闷,不代表我沒有脾氣 我擅于隱藏,不代表我不會悲傷 我擅于活潑较坛,不代表我不會安安靜靜 我擅于笑逐顏開印蔗,不代...
    悲涼的落沒閱讀 111評論 0 1