public class Object { public native int hashCode(); }
当Java代码调用native方法时,JVM将通过 JNI ,调用至对应的C函数
Object.hashCode()就是一个native方法,对应的C函数将计算对象的哈希值,并缓存在 对象头 、 栈上锁记录 (轻量级锁)或者 对象监视锁 (重量级锁,monitor)中,以确保该值在 对象的生命周期之内不会变更
在调用native方法之前,JVM需要将该native方法链接至对应的C函数上
JVM自动查找符合 默认命名规范 的C函数,并且链接起来
package me.zhongmingmao.advanced.jni; public class Foo { int i = 0xDEADBEEF; public static native void foo(); public native void bar(int i, long j); public native void bar(String s, Object o); }
$ javac -h . me/zhongmingmao/advanced/jni/Foo.java $ cat me_zhongmingmao_advanced_jni_Foo.h /* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class me_zhongmingmao_advanced_jni_Foo */ #ifndef _Included_me_zhongmingmao_advanced_jni_Foo #define _Included_me_zhongmingmao_advanced_jni_Foo #ifdef __cplusplus extern "C" { #endif /* * Class: me_zhongmingmao_advanced_jni_Foo * Method: foo * Signature: ()V */ JNIEXPORT void JNICALL Java_me_zhongmingmao_advanced_jni_Foo_foo (JNIEnv *, jclass); /* * Class: me_zhongmingmao_advanced_jni_Foo * Method: bar * Signature: (IJ)V */ JNIEXPORT void JNICALL Java_me_zhongmingmao_advanced_jni_Foo_bar__IJ (JNIEnv *, jobject, jint, jlong); /* * Class: me_zhongmingmao_advanced_jni_Foo * Method: bar * Signature: (Ljava/lang/String;Ljava/lang/Object;)V */ JNIEXPORT void JNICALL Java_me_zhongmingmao_advanced_jni_Foo_bar__Ljava_lang_String_2Ljava_lang_Object_2 (JNIEnv *, jobject, jstring, jobject); #ifdef __cplusplus } #endif #endif
这种链接方式对C函数名没有要求,通常会使用一个名为 registerNatives
的native方法,该方法还是会按照 自动链接
的方式链接到对应的C函数,然后在 registerNatives
对应的C函数中, 手动链接该类的其他native方法
public class Object { // 自动链接 private static native void registerNatives(); static { registerNatives(); } public final native Class<?> getClass(); // 主动链接 public native int hashCode(); public final native void wait(long timeout) throws InterruptedException; public final native void notify(); public final native void notifyAll(); protected native Object clone() throws CloneNotSupportedException; }
static JNINativeMethod methods[] = { {"hashCode", "()I", (void *)&JVM_IHashCode}, {"wait", "(J)V", (void *)&JVM_MonitorWait}, {"notify", "()V", (void *)&JVM_MonitorNotify}, {"notifyAll", "()V", (void *)&JVM_MonitorNotifyAll}, {"clone", "()Ljava/lang/Object;", (void *)&JVM_Clone}, }; JNIEXPORT void JNICALL Java_java_lang_Object_registerNatives(JNIEnv *env, jclass cls) { (*env)->RegisterNatives(env, cls, methods, sizeof(methods)/sizeof(methods[0])); } JNIEXPORT jclass JNICALL Java_java_lang_Object_getClass(JNIEnv *env, jobject this) { if (this == NULL) { JNU_ThrowNullPointerException(env, NULL); return 0; } else { return (*env)->GetObjectClass(env, this); } }
C函数将调用 RegisterNatives API ,注册Object类中其他native方法(不包括getClass)所要链接的C函数,这些C函数的函数名并 不符合默认的命名规则 ,详细的C代码请查阅 Object.c
// foo.c #include <stdio.h> #include "me_zhongmingmao_advanced_jni_Foo.h" JNIEXPORT void JNICALL Java_me_zhongmingmao_advanced_jni_Foo_bar__Ljava_lang_String_2Ljava_lang_Object_2 (JNIEnv *env, jobject thisObject, jstring str, jobject obj) { printf("Hello, World/n"); return; }
通过 gcc 命令将其编译成 动态链接库 ,动态链接库的名字必须以 lib 为前缀,以 .dylib (Linux上为 .so )为扩展名
$ gcc -I$JAVA_HOME/include -I$JAVA_HOME/include/darwin -o libfoo.dylib -shared foo.c
// -Djava.library.path=$PATH_TO_DYLIB public static void main(String[] args) { try { System.loadLibrary("foo"); } catch (UnsatisfiedLinkError e) { e.printStackTrace(); System.exit(1); } new Foo().bar("", ""); }
$ java -Djava.library.path=$PATH_TO_DYLIB me.zhongmingmao.advanced.jni.Foo Hello, World
JNI会将Java层面的 基本类型 以及 引用类型 映射为另一套可供C代码使用的 数据结构
Java类型 C数据结构 -------------------- boolean jboolean byte jbyte char jchar short jshort int jint long jlong float jfloat double jdouble void jvoid
引用类型对应的数据结构之间也存在 继承 关系
jobject |- jclass (java.lang.Class objects) |- jstring (java.lang.String objects) |- jthrowable (java.lang.Throwable objects) |- jarray (arrays) |- jobjectArray (object arrays) |- jbooleanArray (boolean arrays) |- jbyteArray (byte arrays) |- jcharArray (char arrays) |- jshortArray (short arrays) |- jintArray (int arrays) |- jlongArray (long arrays) |- jfloatArray (float arrays) |- jdoubleArray (double arrays)
/* * Class: me_zhongmingmao_advanced_jni_Foo * Method: foo * Signature: ()V */ JNIEXPORT void JNICALL Java_me_zhongmingmao_advanced_jni_Foo_foo (JNIEnv *, jclass); /* * Class: me_zhongmingmao_advanced_jni_Foo * Method: bar * Signature: (IJ)V */ JNIEXPORT void JNICALL Java_me_zhongmingmao_advanced_jni_Foo_bar__IJ (JNIEnv *, jobject, jint, jlong); /* * Class: me_zhongmingmao_advanced_jni_Foo * Method: bar * Signature: (Ljava/lang/String;Ljava/lang/Object;)V */ JNIEXPORT void JNICALL Java_me_zhongmingmao_advanced_jni_Foo_bar__Ljava_lang_String_2Ljava_lang_Object_2 (JNIEnv *, jobject, jstring, jobject);
修改C代码,获取Foo类实例的i字段
JNIEXPORT void JNICALL Java_me_zhongmingmao_advanced_jni_Foo_bar__Ljava_lang_String_2Ljava_lang_Object_2 (JNIEnv *env, jobject thisObject, jstring str, jobject obj) { // JNI中访问实例字段的方式类似于JAVA的反射API jclass cls = (*env)->GetObjectClass(env, thisObject); jfieldID fieldID = (*env)->GetFieldID(env, cls, "i", "I"); jint value = (*env)->GetIntField(env, thisObject, fieldID); printf("Hello, World 0x%x/n", value); return; }
$ java -Djava.library.path=$PATH_TO_DYLIB me.zhongmingmao.advanced.jni.Foo Hello, World 0xdeadbeef
如果尝试获取 不存在 的实例字段j,会抛出异常
$ java -Djava.library.path=$PATH_TO_DYLIB me.zhongmingmao.advanced.jni.Foo Hello, World 0x1 Exception in thread "main" java.lang.NoSuchFieldError: j at me.zhongmingmao.advanced.jni.Foo.bar(Native Method) at me.zhongmingmao.advanced.jni.Foo.main(Foo.java:19)
JNIEXPORT void JNICALL Java_me_zhongmingmao_advanced_jni_Foo_bar__Ljava_lang_String_2Ljava_lang_Object_2 (JNIEnv *env, jobject thisObject, jstring str, jobject obj) { // JNI中访问实例字段的方式类似于JAVA的反射API jclass cls = (*env)->GetObjectClass(env, thisObject); jfieldID fieldID = (*env)->GetFieldID(env, cls, "j", "I"); if((*env)->ExceptionOccurred(env)) { printf("Exception!/n"); (*env)->ExceptionClear(env); } fieldID = (*env)->GetFieldID(env, cls, "i", "I"); jint value = (*env)->GetIntField(env, thisObject, fieldID); // we should put an exception guard here as well. printf("Hello, World 0x%x/n", value); return; }
$ java -Djava.library.path=$PATH_TO_DYLIB me.zhongmingmao.advanced.jni.Foo Exception! Hello, World 0xdeadbeef
转载请注明出处:http://zhongmingmao.me/2019/01/12/jvm-advanced-jni/
访问原文「JVM进阶 -- 浅谈JNI」获取最佳阅读体验并参与讨论