系统性对比JNI中的byte数组传递方式

JNI中从Java往C或者C++中传递byte数组,一般有三个方法:GetByteArrayRegion、GetByteArrayElements、GetPrimitiveArrayCritical。

基本概念和用法

// GetByteArrayRegion - 直接复制特定区域
void GetData1(JNIEnv* env, jbyteArray array) {
    jbyte buffer[1024];
    // 显式复制,立即完成复制
    env->GetByteArrayRegion(array, 0, 1024, buffer);
    // 直接使用本地buffer
    process(buffer);
}

// GetByteArrayElements - 获取数组引用或副本
void GetData2(JNIEnv* env, jbyteArray array) {
    jboolean isCopy;
    // 可能复制也可能不复制
    jbyte* buffer = env->GetByteArrayElements(array, &isCopy);
    if (buffer) {
        process(buffer);
        // 必须释放
        env->ReleaseByteArrayElements(array, buffer, 0);
    }
}

// GetPrimitiveArrayCritical - 获取直接引用
void GetData3(JNIEnv* env, jbyteArray array) {
    jboolean isCopy;
    // 尽量返回直接引用
    void* buffer = env->GetPrimitiveArrayCritical(array, &isCopy);
    if (buffer) {
        // 快速操作
        quickProcess(buffer);
        // 必须尽快释放
        env->ReleasePrimitiveArrayCritical(array, buffer, 0);
    }
}

需要注意的mode参数选择:

  • 0: 将修改内容复制回Java数组并释放缓冲区
  • JNI_COMMIT: 只复制回Java数组,不释放缓冲区
  • JNI_ABORT: 不复制回Java数组,只释放缓冲区

特点对比

特性GetByteArrayRegionGetByteArrayElementsGetPrimitiveArrayCritical
内存复制总是复制可能复制尽量避免复制
GC影响允许GC可能禁止GC
使用限制较少严格
性能中等中等最好
安全性最高需谨慎

适用场景

GetByteArrayRegion:合小数据或部分数据,只需要数组部分数据数据量较小需要确保数据独立副本

GetByteArrayElements:适合大数据操作,需要访问完整数组需要长时间处理需要调用其他JNI函数

GetPrimitiveArrayCritical:适合快速访问,需要最高性能操作简单快速不需要调用其他函数

注意事项

  1. GetPrimitiveArrayCritical调用后,return到java前,必须调用ReleasePrimitiveArrayCritical,否则会报错:”JNI DETECTED ERROR IN APPLICATION: Critical lock held when returning to Java on thread Thread”。这也意味着没有办法通过GetPrimitiveArrayCritical将Java中的数组放入C++缓存,异步使用并释放。
  2. 由于GetByteArrayElements可能可以使用Java中的数组指针,并且也允许GC,所以有可能在使用的时候,已经被GC了,为了避免GC,可以将原jbyteArray通过全局引用保存,并在使用完成后手动调用ReleaseByteArrayElements,代码如下:
// C++端
struct BufferHolder {
    JNIEnv* env;
    jbyteArray array;  // 全局引用
    jbyte* buffer;     // 实际数据指针
    jsize length;
};

// JNI接口
extern "C" {

// 创建并保存引用
JNIEXPORT jlong JNICALL
Java_com_example_VideoDecoder_createBuffer(JNIEnv* env, jobject thiz, jbyteArray array) {
    BufferHolder* holder = new BufferHolder();
    
    // 创建全局引用
    holder->array = (jbyteArray)env->NewGlobalRef(array);
    holder->env = env;
    holder->buffer = env->GetByteArrayElements(holder->array, NULL);
    holder->length = env->GetArrayLength(holder->array);
    
    return reinterpret_cast<jlong>(holder);
}

// 使用buffer
JNIEXPORT void JNICALL
Java_com_example_VideoDecoder_decode(JNIEnv* env, jobject thiz, jlong handle) {
    BufferHolder* holder = reinterpret_cast<BufferHolder*>(handle);
    if (holder && holder->buffer) {
        // 使用holder->buffer进行解码
        decodeFrame(holder->buffer, holder->length);
    }
}

// 主动释放
JNIEXPORT void JNICALL
Java_com_example_VideoDecoder_releaseBuffer(JNIEnv* env, jobject thiz, jlong handle) {
    BufferHolder* holder = reinterpret_cast<BufferHolder*>(handle);
    if (holder) {
        if (holder->buffer) {
            env->ReleaseByteArrayElements(holder->array, holder->buffer, 0);
            holder->buffer = nullptr;
        }
        if (holder->array) {
            env->DeleteGlobalRef(holder->array);
            holder->array = nullptr;
        }
        delete holder;
    }
}

} // extern "C"

视频解码领域

视频解码中延迟是一个重要指标,而实现低延迟的一个重要方式就是零拷贝。同时需要兼顾播放的连续性和防止花屏,需要在C++中实现缓存。所以我认为GetByteArrayElements结合全局引用,是比较合适的选择。

Share

You may also like...

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注