长江商报消息本报讯(记者 常燕 陈晴)昨日上午,腾讯无线互联网技术研发总部在汉正式动工,项目一期投资10亿,用地总面积5.6万平方米,预计2014年建成投入使用。
一向十分低调、甚少出席产品发布、奠基仪式活动的腾讯公司董事会主席兼CEO马化腾、首席行政官陈一丹同时出席奠基仪式。“我们相信,腾讯武汉研发中心建成后将成为腾讯中部最大的研发中心和人才基地,将会有力支撑腾讯移动互联网项目的长远发展。”马化腾如是说。
打造华中地区门户枢纽
腾讯武汉研发中心位于江夏区庙山开发区羊子山。届时,约有2600名员工在此办公,这将极大地提升腾讯在华中地区的产品研发能力,并给当地带来数千个就业岗位。
经过单机电脑、局域网与互联网三大时代后,全球科技产业进入宽带互联网和无线互联网并行的新时代。兴建无线互联网技术研发总部也是腾讯对无线互联网产业的最重要的战略布局之一。
“我们一直很看好武汉的发展潜力。”马化腾昨日在汉表示,希望通过本次投资能在华中地区进行全方位的发展。
腾讯首席行政官陈一丹则表示,无线互联网技术研发总部将是腾讯在华中地区的重要门户枢纽,腾讯将依托武汉的人才、政策和地理等各项优质资源,将武汉移动互联网研发总部和运营中心建成腾讯未来发展的创新引擎和成长助推器。
腾讯将为创业者提供平台
对于腾讯武汉研发中心建成后将涉及的研发业务,相关负责人表示,除汇聚QQ浏览器、HTML5开放平台等无线核心业务的研发外,还有电子商务、互动娱乐等业务部门。此外,还搭建全球浏览器创新中心,给想创业的年轻人提供平台机会。
该人士表示,届时还将在汉搭建无线互联网中心,并配套辅助产业园区扶持中小企业CP。
据相关媒体报道称,全球移动互联网用户2016年将突破29亿,比目前网络用户多出两倍多,其中超过10亿的用户将来自中国。这意味着,该项目是腾讯公 司对无线互联网产业最重要的战略布局之一,腾讯无线网技术研发中心的建成,将为腾讯抓住移动互联网的战略机会打下坚实基础。
截至目前,腾讯、搜狐、华为、亚马逊中国、京东商城、库巴商城、凡客诚品、苏宁易购等多家互联网企业都在武汉建立了分公司或研发中心。
马化腾:哪里人才多就到哪设基地
腾讯研发中心为何会选择武汉?首次来汉的马化腾在接受本报记者专访时表示,腾讯公司有很多员工都来自湖北。
目前,腾讯在北京、上海、深圳、广州、成都均已建立分部或研发中心。马化腾表示,在汉建设互联网研发中心,是想通过落户武汉,将腾讯扎根华中地区,继华南、华北、华东、西南等地之后,进一步完善其在全国的布局,来自全国各地高校的人才,回到家乡附近也会更方便。
马化腾表示,从2009年开始,仅短短3年,中国移动互联网产业在3G技术的快速普及下迎来爆发式增长,中国也已经逐步成为目前全球最大的移动互联网用户市场。随着移动通信技术的不断发展,智能终端用户,特别 是智能手机用户的增加,可以预见到中国移动互联网产业已进入高速成长期,同时产业大格局已经初步形成,这将为整个产业链上的参与者提供更多的机会和挑战。
“我们一定要跟着人才走,哪里人才多,就要在哪里设立基地。”马化腾表示,“在移动互联网高速发展的背景下,腾讯公司必须抓住这一历史机遇,加大对移动 互联网行业的关注和投入。”此次腾讯对武汉地区的战略性投资,就是对移动互联网产业最重要的布局之一,同时也是腾讯公司大力发展移动互联网行业所迈出的坚 实一步。
◇影响
腾讯将给湖北IT业带来“鲶鱼效应”
“虽然此次主要是建设研发中心,但腾讯的到来,对本土企业来说是一个机会。”华中科技大学管理学院教授田志龙表示,腾讯来汉将带动相关区域和产业的快速发展。
作为中国最大的互联网企业,腾讯的落户,将拉动移动互联网上下游产业链,对IT人才、武汉移动互联网产业带来利好。
腾讯有望成汉企“黄埔军校”
“武汉能为腾讯提供发展必须的人才、产业环境资源等各方面支持;腾讯也可以为武汉的发展做出贡献,不仅带动当地高新科技产业发展,还能携手打造和谐共赢的产业生态圈。”谈及此次合作,马化腾认为这是一个双赢的选择。
武汉IT界业内人士直言,任何一个城市必须在行业内有一个领头羊的企业,这样才能逐步形成完整的产业链,形成聚合效应,此次腾讯来汉,就是一个契机。腾讯有望成为武汉企业的“黄埔军校”。
不过,湖北有深谙移动互联网产业的人士则表示,作为信息产业的巨头,腾讯令武汉本土企业又敬又怕。
“不可否认,腾讯现在处于行业领头羊地位,令人敬佩。”不过,上述人士同时表示,腾讯的平台过于强大,若企业与它合作,能站在巨人肩膀上但还是被巨人身影盖住,令人恐惧 。
有业内人士笑称,这种“又爱又怕”的心态会逐步发生变化。武汉IT企业不能像鸵鸟一样把头埋在沙子里,不闻窗外事。IT业的快速变化,需要企业具有国际 视野。“有点鲶鱼效应的意思,腾讯的到来,会让大家都动起来,自发地往前冲,而不是安享在武汉当区域的老大。”该人士如是说。
同时,该人士表示,武汉多数信息相关企业,以面向企业等相对比较专业的客户为主,而腾讯则是面对群体非常庞大的普通网民,二者定位上有区别。他建议,今后,武汉本土企业可以继续发挥自己优势,深耕自己的专业领域。
将带动行业快速发展
“若可以去腾讯工作,我就不用去外地了。”对于腾讯的入驻,2006年毕业的华中科技大学学生小洪非常期待。
小洪说,自己目前在光谷一家互联网相关企业工作,虽然工作多年,工资仍然只有四千元左右。而自己正打算和女朋友买房结婚,但目前收入水平让他捉襟见肘。 “近期,有广东的朋友给我介绍了几个工作机会,薪水比现在高得多,但在武汉生活习惯了,朋友都在这里,实在不想离开。”
事实上,像小洪这样的人很多。据了解,虽然武汉高校资源丰富,但由于沿海地区有更多更好的机会,近年来,很多毕业生还是去了沿海,武汉人才流失现象严重。
腾讯、华为、搜狐等IT龙头企业入驻之后,湖北人才外流的现象将得到根本性的改变。田志龙说,外地房价高,生活成本也高,很多毕业生其实想回来,而腾讯这些大佬的入驻则正好提供了这样一个机会。
不过,也有IT业内人士担忧,相比武汉本土企业,腾讯公司为人才提供的各方面待遇更为优厚,这对于人才来说当然是好事,能吸引并留住人才,但对本土企业来说,却是一种压力。
“此类标志性的公司来汉,会进一步带动湖北籍IT精英的回归。”该人士认为,腾讯、联想、搜狐等IT巨头相继入驻武汉,一批湖北籍的IT精英会随之回到 故里。这些回流的人才,有着不局限于武汉的国际视野。而IT巨头的到来,除了将提高本地IT行业人才的薪资标准,还将带动行业快速发展。
推荐:软件开发者薪资,http://www./other/1391128.html
现在,你知道了如何通过JNI来访问JVM中的基本类型数据和字符串、数组这样的引用类型数据,下一步就是学习怎么样和JVM中任意对象的字段和方法进行交互。比如从本地代码中调用JAVA中的方法,也就是通常说的来自本地方法中的callbacks(回调)。
我们从进行字段访问和方法回调时需要的JNI函数开始讲解。本章的稍后部分我们会讨论怎么样通过一些cache(缓存)技术来优化这些操作。在最后,我们还会讨论从本地代码中访问字段和回调方法时的效率问题。
4.1 访问字段
JAVA支持两种field(字段),每一个对象的实例都有一个对象字段的复制;所有的对象共享一个类的静态字段。本地方法使用JNI提供的函数可以获取和修改这两种字段。先看一个从本地代码中访问对象字段的例子:
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方法创建了一个对象并设置s的值,然后调用本地方法InstanceFieldAccess.accessField在本地代码中打印s的值,并把它修改为一个新值。本地方法返回后,JAVA中把这个值再打印一次,可以看出来,字段s的值已经被改变了。下面是本地方法的实现:
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 访问一个对象字段的流程
为了访问一个对象的实例字段,本地方法需要做两步:
首先,通过在类引用上调用GetFieldID获取field ID(字段ID)、字段名字和字段描述符:
Fid=(*env)->GetFieldID(env,cls,”s”,”Ljava/lang/String;”);
上例中的代码通过在对象引用obj上调用GetObjectClass获取到类引用。一旦获取到字段ID,你就可以把对象和字段ID作为参数来访问字段:
Jstr=(*env)->GetObjectField(env,obj,fid);
因为字符串和数组是特殊的对象,所以我们使用GetObjectField来访问字符串类型的实例字段。除了Get/SetObjectField,JNI还支持其它如GetIntField、SetFloatField等用来访问基本类型字段的函数。
4.1.2 字段描述符
在上一节我们使用过一个特殊的C字符串“Ljava/lang/String”来代表一个JVM中的字段类型。这个字符串被称为JNI field descriptor(字段描述符)。
字符串的内容由字段被声明的类型决定。例如,使用“I”来表示一个int类型的字段,“F”来表示一个float类型的字段,“D”来表示一个double类型的字段,“Z”来表示一个boolean类型的字段等等。
像java.lang.String这样的引用类型的描述符都是以L开头,后面跟着一个JNI类描述符,以分号结尾。一个JAVA类的全名中的包名分隔符“.”被转化成“/”。因此,对于一个字段类型的字段来说,它的描述符是“Ljava/lang/String”。
数组的描述符中包含“]”字符,后面会跟着数组类型的描述符,如“[I”是int[]类型的字段的描述符。12.3.3详细介绍了各种类型的字段描述以及他们代表的JAVA类型。
你可以使用javap工具来生成字段描述符。
4.1.3 访问静态字段
访问静态字段和访问实例字段相似,看下面这个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这个类包含一个静态字段si,main方法创建了一个对象,初始化静态字段,然后调用本地方法StaticFieldAccess.accessField在本地代码中打印静态字段中的值,然后设置新的值,为了演示这个值确实被改变了,在本地方法返回后,JAVA中再次这个静态字段的值。
下面是本地方法StaticFieldAccess.accessField的实现:
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);
}
运行程序可得到输出结果:
In C:
StaticFieldAccess.si = 100
In Java:
StaticFieldAccess.si = 200
访问静态字段和对象实例字段的不同点:
1、 访问静态字段使用GetStaticFieldID,而访问对象的实例字段使用GetFieldID,但是,这两个方法都有相同的返回值类型:jfieldID。
4.2 调用方法
JAVA中有几种不同类型的方法,实例方法必须在一个类的某个对象实例上面调用。而静态方法可以在任何一个对象实例上调用。对于构建方法的调用我们推迟到下一节。
JNI支持一系列完整的函数让你可以在本地代码中回调JAVA方法,下面例子演示了如何从本地代码中调用一个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");
}
}
下面的是本地方法的实现:
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 调用实例方法
本地方法Java_InstanceMethodCall_nativeMethod的实现演示了在本地代码中调用JAVA方法的两步:
1、 本地方法首先调用JNI函数GetMethodID。这个函数在指定的类中寻找相应的方法。这个寻找过程是基于方法描述符的。如果方法不存在,GetMethodID返回NULL。这时,立即从本地方法中返回,并引发一个NoSuchMethodError错误。
2、 本地方法通过调用CallVoidMethod来调用返回值为void的实例方法。
除了CallVoidMethod这个函数以外,JNI也支持对返回值为其它类型的方法的调用。如果你调用的方法返回值类型为int,你的本地方法会使用CallIntMethod。类似地,你可以调用CallObjectMethod来调用返回值为java.lang.String、数组等对象类型的方法。
你也可以使用Call<Type>Method系列的函数来调用接口方法。你必须从接口类型中获取方法ID,下面的代码演示了如何在java.lang.Thread实例上面调用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来获取一个类的引用,在这里,我们可以学到如何获取一个接口的引用。
4.2.2 生成方法描述符
JNI中描述字段使用字段描述符,描述方法同样有方法描述符。一个方法描述符包含参数类型和返回值类型。参数类型出现在前面,并由一对圆括号将它们括起来,参数类型按它们在方法声明中出现的顺序被列出来,并且多个参数类型之间没有分隔符。如果一个方法没有参数,被表示为一对空圆括号。方法的返回值类型紧跟参数类型的右括号后面。
例如,“(I)V”表示这个方法的一个参数类型为int,并且有一个void类回值。“()D”表示这个方法没有参数,返回值类型为double。
方法描述符中可能会包含类描述符(12.3.2),如方法native private String getLine(String);的描述符为:“(Ljava/lang/String;)Ljava/lang/String;”
数组类型的描述符以“[”开头,后面跟着数组元素类型的描述符。如,public static void main(String[] args);的描述符是:"([Ljava/lang/String;)V"
12.3.4详细描述了怎么样生成一个JNI方法描述符。同样,你可以使用javap工具来打印出JNI方法描述符。
4.2.3 调用静态方法
前一个例子演示了一个本地方法怎样调用实例方法。类似地,本地方法中同样可以调用静态方法:
1、 通过GetStaticMethodID获取方法ID。对应于调用实例方法时的GetMethodID。
2、 传入类、方法ID、参数,并调用提供静态方法调用功能的JNI系列函数中的一个,如:CallStaticVoidMethod,CallStaticBooleanMethod等。
调用静态方法和调用实例方法的JNI函数有一个很大的不同,前者第二个参数是类引用,后者是对象实例的引用。
在JAVA访问一个静态方法可以通过类,也可以通过对象实例。而JNI的规定是,在本地代码中回调JAVA中的静态方法时,必须指定一个类引用才行。下面的例子演示了这个用法:
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");
}
}
下面是本地方法的实现:
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);
}
当调用CallStaticVoidMethod时,确保你传入的是类引用cls而不是对象引用obj。运行程序,输出为:
In C
In Java
4.2.4 调用父类的实例方法
如果一个方法被定义在父类中,在子类中被覆盖,你也可以调用这个实例方法。JNI提供了一系列完成这些功能的函数:CallNonvirtual<Type>Method。为了调用一个定义在父类中的实例方法,你必须遵守下面的步骤:
1、 使用GetMethodID从一个指向父类的引用当中获取方法ID。
2、 传入对象、父类、方法ID和参数,并调用CallNonvirtualVoidMethod、CallNonvirtualBooleanMethod等一系列函数中的一个。
这种调用父类实例方法的情况其实很少遇到,通常在JAVA中可以很简单地做到:super.f();
CallNonvirtualVoidMethod也可以被用来调用父类的构造函数。这个在下节就会讲到。
4.3 调用构造函数
JNI中,构造函数可以和实例方法一样被调用,调用方式也相似。传入“<init>”作为方法名,“V”作为返回类型。你可以通过向JNI函数NewObject传入方法来调用构造函数。下面的代码实现了与JNI函数NewString相同的功能:把存储在C缓冲区内的Unicode编码的字符序列,创建成一个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;
}
上面这个本地方法有些复杂,需要详细解释一下。首先,FindClass返回一个java.lang.String类的引用,接着,GetMethodID返回构造函数String(char[] chars)的方法ID。我们调用NewCharArray分配一个字符数组来保存字符串元素。JNI函数NewObject调用方法ID所标识的构造函数。NewObject函数需要的参数有:类的引用、构造方法的ID、构造方法需要的参数。
DeleteLocalRef允许VM释放被局部引用elemArr和stringClass引用的资源。5.2.1中详细描述了调用DeleteLocalRef的时机和原因。
这个例子引出了一个问题,既然我们可以利用JNI函数来实现相同的功能,为什么JNI还需要NewString这样的内置函数?原因是,内置函数的效率远高于在本地代码里面调用构造函数的API。而字符串又是最常用到的对象类型,因此需要在JNI中给予特殊的支持。
你也可以做到通过CallNonvirtualVoidMethod函数来调用构造函数。这种情况下,本地代码必须首先通过调用AllocObject函数创建一个未初始化的对象。上面例子中的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创建了一个未初始化的对象,使用时一定要非常小心,确保一个对象上面,构造函数最多被调用一次。本地代码不应该在一个对象上面调用多次构造函数。有时,你可能会发现创建一个未初始化的对象然后一段时间以后再调用构造函数的方式是很有用的。尽管如此,大部分情况下,你应该使用NewObject,尽量避免使用容易出错的AllocObject/CallNonvirtualVoidMethod方法。
4.4 缓存字段ID和方法ID
获取字段ID和方法ID时,需要用字段、方法的名字和描述符进行一个检索。检索过程相对比较费时,因此本节讨论用缓存技术来减少这个过程带来的消耗。缓存字段ID和方法ID的方法主要有两种。两种区别主要在于缓存发生的时刻,是在字段ID和方法ID被使用的时候,还是定义字段和方法的类静态初始化的时候。
4.4.1 使用时缓存
字段ID和方法ID可以在字段的值被访问或者方法被回调的时候缓存起来。下面的代码中把字段ID存储在静态变量当中,这样当本地方法被重复调用时,不必重新搜索字段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);
}
由于多个线程可能同时访问这个本地方法,上面方法中的代码很可能会导致混乱,其实没事,多个线程计算的ID其实是相同的。
同样的思想,我们也可以缓存java.lang.String的构造方法的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;
}
当MyNewString方法第一次被调用时,我们计算java.lang.String的构造方法的ID,并存储在静态变量cid中。
4.4.2 类的静态初始化过程中缓存字段和方法ID
我们在使用时缓存字段和方法的ID的话,每次本地方法被调用时都要检查ID是否已经被缓存。许多情况下,在字段ID和方法ID被使用前就初始化是很方便的。VM在调用一个类的方法和字段之前,都会执行类的静态初始化过程,所以在静态初始化该类的过程中计算并缓存字段ID和方法ID是个不错的选择。
例如,为了缓存InstanceMethodCall.callback的方法ID,我们引入了一个新的本地方法initIDs,这个方法在InstanceMethodCall的静态初始化过程中被调用。代码如下:
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节中的代码相比,上面这段代码多了两行,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进行静态初始化时在调用任何方法前调用initIDs,这样方法ID就被缓存了全局变量中,本地方法的实现就不必再进行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当中,我们不能在String类中插入一个initIDs方法。
比起静态初始时缓存来说,使用时缓存有一些缺点:
1、 使用时缓存的话,每次使用时都要检查一下。
2、 方法ID和字段ID在类被unload时就会失效,如果你在使用时缓存ID,你必须确保只要本地代码依赖于这个ID的值,那么这个类不被会unload(下一章演示了如何通过使用JNI函数创建一个类引用来防止类被unload)。另一方面,如果缓存发生在静态初始化时,当类被unload和reload时,ID会被重新计算。
因此,尽可能在静态初始化时缓存字段ID和方法ID。
4.5 JNI操作JAVA中的字段和方法时的效率
学完了如何缓存ID来提高效率后,你可能会对使用JNI访问java字段和方法的效率不太明白,native/java比起java/native和java/java来的话,效率如何呢?
当然,这取决于VM的实现。我们不能给出在大范围的VM上通用的数据,但我们可以通过分析本地方法回调java方法和JNI操作字段以及方法的过程来给出一个大致的概念。
我们从比较java/native和java/java的效率开始。java/native调用比java/java要慢,主要有以下几个原因:
1、 java/native比起JVM内部的java/java来说有一个调用转换过程,在把控制权和入口切换给本地方法之前,VM必须做一些额外的操作来创建参数和栈帧。
2、 对VM来说,对方法调用进行内联比较容易,而内联java/native方法要难得多。
据我们的估计,VM进行java/native调用时的消耗是java/java的2~3倍。当然VM可以进行一些调整,使用java/native的消耗接近或者等于java/java的消耗。
技术上来讲,native/java调用和java/native是相似的。但实际上native/java调用很少见,VM通常不会优化native/java这种回调方式。多数VM中,native/java调用的消耗可以达到java/java调用的10倍。
使用JNI访问字段的花费取决于通过JNIEnv进行调用的消耗。以废弃一个对象引用来说,本地代码必须依赖于特定的JNI函数才能做到,而这个依赖是必须的,它把本地代码和VM中对象的内部形式很好地隔离开。
这四种情况下你会用到本书:
1、 在Java程序中复用以前写过的C/C++代码。
2、 自己实现一个java虚拟机
3、 学习不同语言如何进行协作,尤其是如何实现垃圾回收和多线程。
4、 把一个虚拟机实现整合到用C/C++写的程序中。
本书是写给开发者的。JNI在1997年第一次发布,本书总结了SUN工程师和大量开发者两年来积累的经验。
本书介绍了JNI的设计思想,对这种思想的理解是使用JNI的各种特性的基础。
本书有一部分是JAVA2平台上面的JNI特征的规范说明。JNI程序员可以把这部分用作一个手册。JVM开发者在实现虚拟机的时候必须遵守这些规范。
JNI的部分设计思想来源于Netscape的Java Runtime Interface(JRI)。