JNI代码实践

2018-12-17  本文已影响0人  云佾风徽

JNI代码实践

[TOC]

说明

关于jni代码的cmake构建脚本,kotlin如何声明和使用native方法,jni层如何进行socket通讯,jni层如何进行多线程操作,请参见我的另一篇文章JNI入门

  1. reference的练习与观察: TestNativeReference
  2. jni.h声明的api操作java类与对象+动态注册jni接口方法: TestNativeInterface

Local Reference / Global Reference / Global Weak Reference

Local Reference 的生命期

native method 的执行期(从 Java 程序切换到 native code 环境时开始创建,或者在 native method 执行时调用 JNI function 创建),在 native method 执行完毕切换回 Java 程序时,所有 JNI Local Reference 被删除,生命期结束(调用 JNI function 可以提前结束其生命期)。

local ref 创建的时机

  1. java层调用native方法,进入native上下文环境会初始化一些jobject及子类的local ref
  2. native方法执行期间,调用了jni.h中的jni接口方法创建出的对象比如NewObject,会产生local ref

local ref销毁的时机

  1. java层调用的native方法执行完毕,return返回,切换回java层,所有local ref都销毁
  2. 调用jni.h中的jni接口可以提前销毁local ref ,例如DeleteLocalRef

Local Reference tabel

java环境下,调用声明的jni方法,从当前线程切换到native code上下文环境。

此时JVM会在当前线程的本地方法栈(native method stack,线程独立的内存区域,线程共享的是gc堆和方法区)分配一块内存,创建发起的jni方法的生命周期内的Local Reference tabel。

用于存放本次native code执行中创建的所有Local Reference,每当在native code中创建一个新的jobject及其子类对象,就会往该表中添加一条引用记录。

image

引用数量上限

每次进入native创建的local ref表有引用数量的上限,如果超过上限则会异常崩溃。

extern "C"
JNIEXPORT void JNICALL
Java_cn_rexih_android_testnativeinterface_MainActivity_testLocalRefOverflow(JNIEnv *env, jobject instance) {

    char a[5];
    // 进入native环境时local ref table就会存在几条引用,所以还没到512时就会溢出
    for (int i = 0; i < 512; ++i) {
        __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "before:%d", i);
        sprintf(a, "%d", i);
        jstring pJstring = env->NewStringUTF(a);
        __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "after:%d", i);
    }
    __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "test finish");

}

dalvik虚拟机执行时崩溃,日志信息

JNI ERROR (app bug): local reference table overflow (max=512)
JNI local reference table (0xb98680a0) dump:
  Last 10 entries (of 512):
      511: 0xa4fcaea0 java.lang.String "504"
      510: 0xa4fcae68 java.lang.String "503"
      509: 0xa4fcae30 java.lang.String "502"
      508: 0xa4fcadf8 java.lang.String "501"
      507: 0xa4fcadc0 java.lang.String "500"
      506: 0xa4fcad88 java.lang.String "499"
      505: 0xa4fcad50 java.lang.String "498"
      504: 0xa4fcad18 java.lang.String "497"
      503: 0xa4fcace0 java.lang.String "496"
      502: 0xa4fcaca8 java.lang.String "495"
  Summary:
        3 of java.lang.Class (3 unique instances)
      507 of java.lang.String (507 unique instances)
        1 of java.lang.String[] (2 elements)
        1 of cn.rexih.android.testnativeinterface.MainActivity
Failed adding to JNI local ref table (has 512 entries)

art虚拟机崩溃日志

    --------- beginning of crash
2018-12-09 15:55:58.062 23487-23487/? A/libc: stack corruption detected (-fstack-protector)
2018-12-09 15:55:58.062 23487-23487/? A/libc: Fatal signal 6 (SIGABRT), code -6 (SI_TKILL) in tid 23487 (nativeinterface), pid 23487 (nativeinterface)

dump local reference

代码强制进行 reference table dump

extern "C" JNIEXPORT void JNICALL
Java_cn_rexih_android_testnativeinterface_MainActivity_testInitLocalRef(JNIEnv *env, jobject instance, jobject entity) {

    // 标记
    jstring pMark = env->NewStringUTF("I'm a mark");

    // 强制进行 reference table dump
    // 查找了一次VMDebug类,会添加到local ref table中
    jclass vm_class = env->FindClass("dalvik/system/VMDebug");
    jmethodID dump_mid = env->GetStaticMethodID(vm_class, "dumpReferenceTables", "()V");
    env->CallStaticVoidMethod(vm_class, dump_mid);

}

参见Android JNI local reference table, dump current state

虽然有的post说art不能用dalvik.system.VMDebug,但实测在9.0的虚拟机上仍然可以调用

dalvik的local reference表记录(4.4设备)

dalvik虚拟机,在从java环境调用jni方法进入native环境时,会将

  1. 入参的jobject或者jclass(表示jni方法所在实例/类)

  2. 其他入参对应的jobject参数

添加到本次的local reference表中

--- reference table dump ---
JNI local reference table (0xb986f120) dump:
  Last 10 entries (of 10):
        9: 0xa4c881a8 java.lang.Class<dalvik.system.VMDebug>
        8: 0xa4fae008 java.lang.String "I'm a mark"
        7: 0xa4fadfe8 cn.rexih.android.testnativeinterface.entity.Service
        6: 0xa4f70f88 cn.rexih.android.testnativeinterface.MainActivity
        5: 0xa4ce59f0 java.lang.Class<com.android.internal.os.ZygoteInit>
        4: 0xa4cffaf0 java.lang.String "start-system-ser... (19 chars)
        3: 0xa4cffa78 java.lang.String "com.android.inte... (34 chars)
        2: 0xa4cffa60 java.lang.String[] (2 elements)
        1: 0xa4c840e0 java.lang.Class<java.lang.String>
        0: 0xa4c831e8 java.lang.Class<java.lang.Class>
  Summary:
        4 of java.lang.Class (4 unique instances)
        3 of java.lang.String (3 unique instances)
        1 of java.lang.String[] (2 elements)
        1 of cn.rexih.android.testnativeinterface.MainActivity
        1 of cn.rexih.android.testnativeinterface.entity.Service
JNI global reference table (0xb9868e10) dump:
  Last 10 entries (of 258):
      // ...

art的local reference表记录(9.0设备)

local ref表的记录与4.4版本dalvik的虚拟机不同,不会把表示jni方法所在实例/类的对象,和其他入参对象对应的jobject类添加到本次的local reference表中

Accessing hidden method Ldalvik/system/VMDebug;->dumpReferenceTables()V (light greylist, JNI)
--- reference table dump ---
local reference table dump:
  Last 8 entries (of 8):
        7: 0x6fcf8db0 java.lang.Class<dalvik.system.VMDebug>
        6: 0x12c72160 java.lang.String "I'm a mark"
        5: 0x7000ed68 java.lang.Class<com.android.internal.os.ZygoteInit>
        4: 0x74314fa8 java.lang.String "--abi-list=x86"
        3: 0x74314f80 java.lang.String "start-system-ser... (19 chars)
        2: 0x7430d4c8 java.lang.String "com.android.inte... (34 chars)
        1: 0x7430d290 java.lang.String[] (3 elements)
        0: 0x6fadbe58 java.lang.Class<java.lang.String>
  Summary:
        4 of java.lang.String (4 unique instances)
        3 of java.lang.Class (3 unique instances)
        1 of java.lang.String[] (3 elements)
monitors reference table dump:
  (empty)
global reference table dump:
  Last 10 entries (of 597):
      // ...

参考资料

IBM J2N

EnsureLocalCapacity

有post说可以使用EnsureLocalCapacity避免溢出,通过实际操作来理解:

extern "C"
JNIEXPORT void JNICALL
Java_cn_rexih_android_testnativeinterface_MainActivity_testEnsureLocalCapacity(JNIEnv *env, jobject instance) {

    char a[5];
    int capacity = 516;
    // 已知在dalvik里local ref 可以有512,通过不断尝试查找可用的上限
    for (; capacity > 0 ; --capacity) {
        __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "current try alloc :%d", capacity);
        if(0 > env->EnsureLocalCapacity(capacity)){
            // 内存分配失败, 调用ExceptionOccurred也会返回一个jobject占用local ref table,须释放
            jthrowable pJthrowable = env->ExceptionOccurred();
            env->ExceptionDescribe();
            env->ExceptionClear();
            env->DeleteLocalRef(pJthrowable);
        } else {
            __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "success alloc :%d", capacity);
            break;
        }
    }
    env->CallStaticVoidMethod(g_vm_class, g_dump_mid);
    for (int i = 0; i < capacity; ++i) {
        __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "before:%d", i);
        sprintf(a, "%d", i);
        jstring pJstring = env->NewStringUTF(a);
        __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "after:%d", i);
    }
    __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "test finish");
    env->CallStaticVoidMethod(g_vm_class, g_dump_mid);
}

运行结果:

I/JNI_TEST: current try alloc :506
W/System.err: java.lang.OutOfMemoryError: can't ensure local reference capacity
// ...
I/JNI_TEST: current try alloc :505
I/JNI_TEST: success alloc :505
I/dalvikvm: --- reference table dump ---
W/dalvikvm: JNI local reference table (0xb970b250) dump:
W/dalvikvm:   Last 7 entries (of 7):
W/dalvikvm:         6: 0xa4f73d40 cn.rexih.android.testnativeinterface.MainActivity
W/dalvikvm:         5: 0xa4ce59f0 java.lang.Class<com.android.internal.os.ZygoteInit>
W/dalvikvm:         4: 0xa4cffaf0 java.lang.String "start-system-ser... (19 chars)
W/dalvikvm:         3: 0xa4cffa78 java.lang.String "com.android.inte... (34 chars)
W/dalvikvm:         2: 0xa4cffa60 java.lang.String[] (2 elements)
W/dalvikvm:         1: 0xa4c840e0 java.lang.Class<java.lang.String>
W/dalvikvm:         0: 0xa4c831e8 java.lang.Class<java.lang.Class>
W/dalvikvm:   Summary:
W/dalvikvm:         3 of java.lang.Class (3 unique instances)
W/dalvikvm:         2 of java.lang.String (2 unique instances)
W/dalvikvm:         1 of java.lang.String[] (2 elements)
W/dalvikvm:         1 of cn.rexih.android.testnativeinterface.MainActivity
W/dalvikvm: JNI global reference table (0xb9865d40) dump:
W/dalvikvm:   Last 10 entries (of 261):
// ...

从测试结果可知:

  1. 使用EnsureLocalCapacity也不能打破jvm的Local ref引用数量上限

  2. EnsureLocalCapacity主要作用是检查即将创建的Local ref是否会超过上限

  3. 从测试中可以得到如下算式:

    传入EnsureLocalCapacity的最大capacity =

    JVM的Local ref引用数量上限 - 当前已存在的Local ref数量

PushLocalFrame/PopLocalFrame

extern "C"
JNIEXPORT void JNICALL
Java_cn_rexih_android_testnativeinterface_MainActivity_testPushLocalFrame(JNIEnv *env, jobject instance) {

    char a[5];
    int capacity = 516;
    for (; capacity > 0; --capacity) {
        __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "current try alloc :%d", capacity);
        if (0 > env->PushLocalFrame(capacity)) {
            env->ExceptionClear();
        } else {
            __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "success alloc :%d", capacity);
            break;
        }
    }
    env->CallStaticVoidMethod(g_vm_class, g_dump_mid);
    jobject lastVal;
    for (int i = 0; i < capacity; ++i) {
        __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "before:%d", i + 1);
        sprintf(a, "%d", i + 1);
        lastVal = env->NewStringUTF(a);
        __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "after:%d:addr:0x%x", i + 1, lastVal);
    }
    __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "test finish");
    jobject convertThenRetain = env->PopLocalFrame(lastVal);
    env->CallStaticVoidMethod(g_vm_class, g_dump_mid);
}
before:504
after:504:addr:0x1d2007f9
before:505
// 临时帧与前一帧中505的local ref地址不同
after:505:addr:0x1d2007fd
test finish
--- reference table dump ---
JNI local reference table (0xb9858520) dump:
  Last 8 entries (of 8):
        // 临时帧与前一帧中505的local ref地址不同
        7: 0xa4fd0380 java.lang.String "505"
        6: 0xa4f759b8 cn.rexih.android.testnativeinterface.MainActivity
        5: 0xa4ce59f0 java.lang.Class<com.android.internal.os.ZygoteInit>
        4: 0xa4cffaf0 java.lang.String "start-system-ser... (19 chars)
        3: 0xa4cffa78 java.lang.String "com.android.inte... (34 chars)
        2: 0xa4cffa60 java.lang.String[] (2 elements)
        1: 0xa4c840e0 java.lang.Class<java.lang.String>
        0: 0xa4c831e8 java.lang.Class<java.lang.Class>
  Summary:
        3 of java.lang.Class (3 unique instances)
        3 of java.lang.String (3 unique instances)
        1 of java.lang.String[] (2 elements)
        1 of cn.rexih.android.testnativeinterface.MainActivity
JNI global reference table (0xb9872390) dump:
  Last 10 entries (of 261):
      // ...

Local Reference的代码实践

  1. local reference仅在本次jni方法调用过程中有效,jni方法执行完毕返回java层后,local reference失效,所以不能直接把local ref保存为全局变量。如果有需要可以使用Global/Global Weak ref保存为全局变量

  2. 不能产生大量的Local ref,尤其是在循环结构中,否则会造成table overflow ,每次循环结束,如果不再使用当前的local ref,应当及时删除

    env->DeleteLocalRef(pJstring);
    
  3. 按照使用场景,可以使用EnsureLocalCapacity或者Push/PopLocalFrame来控制Local Ref的规模或者管理销毁。

Global Reference

如果需要将某一个jobject及其子类转换成全局变量,必须使用Global Ref,但是必须始终跟踪全局引用,并确保不再需要对象时删除它们。

JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
    JNIEnv *env = NULL;
    if (JNI_OK != vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6)) {
        return -1;
    }
    g_vm_class = static_cast<jclass>(env->NewGlobalRef(env->FindClass("dalvik/system/VMDebug")));
    g_dump_mid = env->GetStaticMethodID(g_vm_class, "dumpReferenceTables", "()V");
    return JNI_VERSION_1_6;
}

JNIEXPORT void JNI_OnUnload(JavaVM *vm, void *reserved) {
    JNIEnv *env = NULL;
    if (JNI_OK != vm->GetEnv(reinterpret_cast<void **>(env), JNI_VERSION_1_6)) {
        return;
    }
    env->DeleteGlobalRef(g_vm_class);
    g_dump_mid = NULL;
}

Global Weak Reference

IsSameObject

可以判断global/global weak/local ref指向的是不是同一个对象

extern "C"
JNIEXPORT jboolean JNICALL
Java_cn_rexih_android_testnativeinterface_MainActivity_testIsSameObject(JNIEnv *env, jobject instance) {
    return env->IsSameObject(g_vm_class, env->FindClass("dalvik/system/VMDebug"));
}

临界区操作api的说明

有一些问题,暂时不考虑使用,参见JVM Anatomy Park #9: JNI 临界区 与 GC 锁

字符串操作

string_function.png

native string 转 java string

jstring test = env->NewStringUTF("test");

获取java字符串长度

env->GetStringUTFLength(test);

java string 转 native string

choose_string_function.png

字符串释放

extern "C"
JNIEXPORT jstring JNICALL
Java_cn_rexih_android_testnativereference_JniManager_echo(JNIEnv *env, jobject instance, jstring text) {
    // java string -> const char*
    const char *byChars = env->GetStringUTFChars(text, NULL);

    __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "by GetStringUTFChars:%s", byChars);
    jstring pTemp = env->NewStringUTF(byChars);
    env->DeleteLocalRef(pTemp);
    
    env->ReleaseStringUTFChars(text, byChars);

    jsize len = env->GetStringUTFLength(text);
    char *regionBuf = new char[len];
    env->GetStringUTFRegion(text, 0, len, regionBuf);

    // const char* -> java string
    reverseChars(regionBuf);
    jstring pRet = env->NewStringUTF(regionBuf);
    delete regionBuf;
    return pRet;
}

编码转换

  1. 当前用ndk 18,cmake方式编译,中文字符串可以直接转换,不会出现乱码
  2. 如果需要转码,在native层通过FindClass使用String类的api来进行编码转换
  3. 如果确有需要使用这种方式,可以考虑把jclass和jmethodid缓存起来使用。

数组操作

array_function.png

基本类型数组

choose_array_function.png
  1. 如果要对一整块数据操作,可以考虑用Get<type>ArrayElements方法直接获取整块数据,比起反复获取少量数据效率更好,数据使用完后必须调用Release方法进行释放

  2. 如果有预分配的缓冲区或者只对部分数据进行操作,考虑用Get<type>ArrayRegion,将数据复制到缓冲区使用。不需要Release释放

    extern "C"
    JNIEXPORT jint JNICALL
    Java_cn_rexih_android_testnativereference_JniManager_testRegionArray(JNIEnv *env, jobject instance, jcharArray carr) {
    
        jsize alen = env->GetArrayLength(carr);
        jchar *pChar = new jchar[alen];
        env->GetCharArrayRegion(carr, 0, alen, pChar);
    
        int sum = 0;
    
        for (int i = 0; i < alen; ++i) {
            sum += (int) (pChar[i] - '0');
        }
        printRefTable(env);
        return sum;
    
    }
    

isCopy/JNI_COMMIT/JNI_ABORT(同步处理)

extern "C" JNIEXPORT jint JNICALL
Java_cn_rexih_android_testnativereference_JniManager_testArrayReleaseMode(
        JNIEnv *env, jobject instance, jintArray test, jint option) {

    jboolean isCopy;
    jint *pInt = env->GetIntArrayElements(test, &isCopy);
    __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "GetIntArrayElements isCopy: %s", isCopy ? "true" : "false");
    printRefTable(env);
    int a;
    switch (option) {
        case 0:
            // 0
            pInt[2] = pInt[2] + 12;
            env->ReleaseIntArrayElements(test, pInt, 0);
            printRefTable(env);
            a = pInt[1];
            break;
        case JNI_COMMIT:
            // commit
            pInt[2] = pInt[2] + 12;
            env->ReleaseIntArrayElements(test, pInt, JNI_COMMIT);
            printRefTable(env);
            a = pInt[1];
//            env->ReleaseIntArrayElements(test, pInt, JNI_ABORT);
//            printRefTable(env);
            break;
        case JNI_ABORT:
            // abort
            pInt[2] = pInt[2] + 12;
            env->ReleaseIntArrayElements(test, pInt, JNI_ABORT);
            printRefTable(env);
            a = pInt[1];

            break;
    }
    return a;
}

在9.0设备上,因为使用的是copy数组,所以local ref不需要特地说明

在4.4设备上,使用的是copy数组,观察ref table

// pinned数组,在ref table中有特殊记录:
JNI pinned array reference table (0xb96f4060) dump:
  Last 1 entries (of 1):
        0: 0xa4fd1008 int[] (4 elements)
  Summary:
        1 of int[] (4 elements)
        
// abort模式释放后 pinned数组 un-pinned
JNI pinned array reference table (0xb96f4060) dump:
  (empty)
// commit模式释放后 pinned数组 不会 un-pinned
JNI pinned array reference table (0xb96f4060) dump:
  Last 1 entries (of 1):
        0: 0xa4fd1008 int[] (4 elements)
  Summary:
        1 of int[] (4 elements)

二维数组和对象数组

二位数组的每一维数组也是对象。

extern "C"
JNIEXPORT void JNICALL
Java_cn_rexih_android_testnativereference_JniManager_testObjectArray(JNIEnv *env, jobject instance, jobjectArray objArray) {

    jstring pCurJstring;
    const char *pTmpChar;
    jsize alen = env->GetArrayLength(objArray);
    char **pCharArray = static_cast<char **>(malloc(sizeof(char *) * alen));
    for (int i = 0; i < alen; ++i) {
        pCurJstring = static_cast<jstring>(env->GetObjectArrayElement(objArray, i));
        pTmpChar = env->GetStringUTFChars(pCurJstring, NULL);

        __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "before %d: %s", i, pTmpChar);

        size_t clen = strlen(pTmpChar);
        char *pCpChar = static_cast<char *>(malloc(sizeof(char) * clen));
        strcpy(pCpChar, pTmpChar);
        reverseChars(pCpChar);

        pCharArray[alen - 1 - i] = pCpChar;

        env->ReleaseStringUTFChars(pCurJstring, pTmpChar);
        env->DeleteLocalRef(pCurJstring);
    }

    for (int i = 0; i < alen; ++i) {
        __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "after %d: %s", i, pCharArray[i]);
        free(pCharArray[i]);
    }
    free(pCharArray);
    printRefTable(env);
    return;
}

java集合操作

集合对象转换到jni方法的入参是jobject,使用集合可以通过两种方式:

  1. 通过classId和methodid来调用java层的api以获取和操作元素对象。转载一个demo

    // ...
    jclass cls_list = env->GetObjectClass(objectList);
    // ...
    jmethodID list_get = env->GetMethodID(cls_list, "get", "(I)Ljava/lang/Object;");
    jmethodID list_size = env->GetMethodID(cls_list, "size", "()I");
    // ...
    int len = static_cast<int>(env->CallIntMethod(objectList, list_size));
    // ...
    for (int i=0; i < len; i++) {
        jfloatArray element = (jfloatArray)(env->CallObjectMethod(objectList, list_get, i));
        
        float* f_arrays = env->GetFloatArrayElements(element,NULL);
        int arr_len = static_cast<int>(env->GetArrayLength(element));
        for(int j = 0; j < arr_len ; j++){
            printf("\%f \n", f_arrays[j]);
        }
        env->ReleaseFloatArrayElements(element, f_arrays, JNI_ABORT);
        
        env->DeleteLocalRef(element);
    }
    
  2. 对集合对象调用toArray(T[])方法转换成对象数组后再传入jni方法

Exception处理

第一种有缺陷的方式:

if (0 > env->EnsureLocalCapacity(capacity)) {
    // 内存分配失败, 调用ExceptionOccurred也会返回一个jobject占用local ref table,须释放
    jthrowable pJthrowable = env->ExceptionOccurred();
    env->ExceptionDescribe();
    env->ExceptionClear();
    env->DeleteLocalRef(pJthrowable);
}

JNI ExceptionCheck 函数是比 ExceptionOccurred 调用更好的异常检查方式,因为 ExceptionOccurred 调用必须创建局部引用。

参见IBM 处理异常

if (env->ExceptionCheck()) {
    env->ExceptionDescribe();
    env->ExceptionClear();
}

异常发生时可以调用的jni api

当异常待处理时,不能调用大多数JNI函数。您的代码应该会注意到异常(通过函数的返回值,ExceptionCheck或ExceptionOccurred)并返回,或者清除异常并处理它。
当异常挂起时,您允许调用的JNI函数有:

c++异常支持 TODO

信号量捕获异常TODO

参见JNI Crash:异常定位与捕获处理

native操作java类与对象

处理Class

创建Java对象

调用Java方法

操作Java成员变量

主要就是获取fieldID后get/set

GetFieldID
GetStaticFieldID

Get<Type>Field
Set<Type>Field

GetStatic<Type>Field
SetStatic<Type>Field

反射相关

可以传入method/field对象,获取到methodID/fieldID,也可以反过来

jfieldID    (*FromReflectedField)(JNIEnv*, jobject);
jmethodID   (*FromReflectedMethod)(JNIEnv*, jobject);

jobject     (*ToReflectedMethod)(JNIEnv*, jclass, jmethodID, jboolean);
jobject     (*ToReflectedField)(JNIEnv*, jclass, jfieldID, jboolean);

NIO相关TODO

等了解java层的nio后再来看

jobject     (*NewDirectByteBuffer)(JNIEnv*, void*, jlong);
void*       (*GetDirectBufferAddress)(JNIEnv*, jobject);
jlong       (*GetDirectBufferCapacity)(JNIEnv*, jobject);

参考资料

JNI.h解析

JNI的数据类型和类型签名

优化总结

正确性缺陷

正确性技巧

  1. 仅在相关的单一线程中使用 JNIEnv
  2. 在发起可能会导致异常的 JNI 调用后始终检测异常。
  3. 始终检测 JNI 方法的返回值,并包括用于处理错误的代码路径。
  4. 不要忘记为每个 Get*XXX*() 使用模式 0(复制回去并释放内存)调用 Release*XXX*()
  5. 确保代码不会在 Get*XXX*Critical()Release*XXX*Critical() 调用之间发起任何 JNI 调用或由于任何原因出现阻塞。
  6. 不得将局部引用保存在全局变量中
  7. 始终跟踪全局引用,并确保不再需要对象时删除它们。

性能缺陷

性能技巧

  1. 查找并全局缓存常用的类、字段 ID 和方法 ID。
  2. 获取和更新仅本机代码需要的数组部分。在只要数组的一部分时通过适当的 API 调用来避免复制整个数组。
  3. 在单个 API 调用中尽可能多地获取或更新数组内容。如果可以一次较多地获取和更新数组内容,则不要逐个迭代数组中的元素。
  4. 如果可能,将各参数传递给 JNI 本机代码,以便本机代码回调 JVM 获取所需的数据。
  5. 定义 Java 代码与本机代码之间的界限,最大限度地减少两者之间的互相调用。
  6. 构造应用程序的数据,使它位于界限的正确的侧,并且可以由使用它的代码访问,而不需要大量跨界调用。
  7. 当本机代码造成创建大量本地引用时,在各引用不再需要时删除它们。
  8. 如果某本机代码将同时存在大量本地引用,则调用 JNI EnsureLocalCapacity()方法通知 JVM 并允许它优化对本地引用的处理。

缓存ID的陷阱

D类定义
// Trouble in the absence of ID caching
class D extends C {
    private int i;
    D() {
        f(); // inherited from C
    }
}
C类定义
class C {
    private int i;
    native void f();
    private static native void initIDs();
    static {
        initIDs(); // Call an initializing native method
    }
}
本地JNI代码
   static jfieldID FID_C_i;

   JNIEXPORT void JNICALL
   Java_C_initIDs(JNIEnv *env, jclass cls) {

       /* Get IDs to all fields/methods of C that
          native methods will need. */

       FID_C_i = (*env)->GetFieldID(env, cls, "i", "I");
   }

   JNIEXPORT void JNICALL
   Java_C_f(JNIEnv *env, jobject this) {

       ival = (*env)->GetIntField(env, this, FID_C_i);

       ... /* ival is always C.i, not D.i */
   }

其他

参考资料

IBM JNI核对表

misc

jni c与c++区别

#if defined(__cplusplus)
//C++ JNIEnv定义为_JNIEnv结构体
typedef _JNIEnv JNIEnv;
typedef _JavaVM JavaVM;
#else
//c JNIEnv定义为JNINativeInterface结构体指针
typedef const struct JNINativeInterface* JNIEnv;
typedef const struct JNIInvokeInterface* JavaVM;
#endif
struct _JNIEnv {
    const struct JNINativeInterface* functions;
#if defined(__cplusplus)
    // c++版本中,_JNIENV持有一个JNINativeInterface*成员变量,所有jni方法省略了第一个env参数,改为使用this指针
    jint GetVersion()
    { return functions->GetVersion(this); }
#endif /*__cplusplus*/
};

参见androidNDK开发中c与C++的细小区别

同步代码块TODO

jint        (*MonitorEnter)(JNIEnv*, jobject);
jint        (*MonitorExit)(JNIEnv*, jobject);

在程序中集成JVM需要注意的JNI特征

c预编译指令,可变参TODO

va_list 、va_start、 va_arg、 va_end 使用说明

预编译处理——#和##操作符的使用分析

#define宏定义可变参数的使用

C语言--预编译

jboolean的陷阱

java层持久化c++对象

将c++对象指针地址以long返回到java层保存。

参见java 层调用Jni(Ndk) 持久化c c++ 对象

参考资料

JNI tips原版,JNI tips翻译

JNI官方规范中文版

在 JNI 编程中避免内存泄漏 对理解jni引用类型有很大帮助

使用 Java Native Interface 的最佳实践避免最常见的 10 大 JNI 编程错误的技巧和工具

上一篇下一篇

猜你喜欢

热点阅读