android-ndk - 为什么会出现本地引用表溢出?

标签 android-ndk java-native-interface

我正在尝试弄清楚如何解决当提示点数量很多时我的代码发生的崩溃。我的测试每次都使用 530 个提示项重现它。每个提示的 dataSize 为 1。

你可以在下面看到我的代码。本质上,native_update_cue_points 是从 java 层调用的。这样就触发了jni层对java层的多次调用。这不知何故导致表溢出。我在想,每次调用静态 java 函数都不会释放分配的字符串值,从而导致堆栈溢出?

示例代码

static void native_update_cue_points(JNIEnv *env, jclass clazz)
{
    if (smpMediaPlayer)
    {
        std::list<Cue> cuePoints;

        smpMediaPlayer->getCuePoints(&cuePoints);

        for (std::list<Cue>::iterator it = cuePoints.begin(); it != cuePoints.end(); it++)
        {
            Cue cue = *it;

            java_update_cue_point(env, clazz, smFields.global_ref_thiz, 0, &cue);
        }
    }
}

static void java_update_cue_point (JNIEnv *env, jclass clazz, jobject thiz, int reqType, Cue *pCue)
{
    if (!pCue) {
        return;
    }

    jlong id = pCue->id;
    jint type = pCue->type;
    jint extra = pCue->extra;
    jlong pos = pCue->pos;
    jlong duration = pCue->duration;
    jobjectArray jkeys = NULL;
    jobjectArray jvalues = NULL;

    int dataSize = pCue->data.size();

    if (0 < dataSize)
    {
        jkeys = env->NewObjectArray(dataSize, env->FindClass("java/lang/String"), NULL);
        jvalues = env->NewObjectArray(dataSize, env->FindClass("[B"), NULL);

        int position = 0;
        for (std::map<std::string, std::string>::iterator it = pCue->data.begin(); it != pCue->data.end(); it++)
        {
            // set key
            env->SetObjectArrayElement(jkeys, position, env->NewStringUTF(it->first.c_str()));

            // set value
            jbyteArray byteArray = env->NewByteArray(it->second.length());
            env->SetByteArrayRegion(byteArray, 0, it->second.length(), (const signed char *)it->second.c_str());
            env->SetObjectArrayElement(jvalues, position, byteArray);
            env->DeleteLocalRef(byteArray);

            position++;
        }
    }

    // report update
    env->CallStaticVoidMethod(
                clazz,
                smFields.native_callback_add_cue_point,
                thiz,
                (jint)reqType, id, type, extra, pos, duration, jkeys, jvalues);
}

堆栈跟踪

JNI ERROR (app bug): local reference table overflow (max=512)
 local reference table dump:
   Last 10 entries (of 512):
       511: 0x13054090 java.lang.String[] (1 elements)
       510: 0x70732980 java.lang.Class<java.lang.String>
       509: 0x130517c0 java.lang.String "song_artist"
       508: 0x13054080 byte[][] (1 elements)
       507: 0x7079af18 java.lang.Class<byte[]>
       506: 0x13054070 java.lang.String[] (1 elements)
       505: 0x70732980 java.lang.Class<java.lang.String>
       504: 0x13051700 java.lang.String "song_artist"
       503: 0x12d17ff0 byte[][] (1 elements)
       502: 0x7079af18 java.lang.Class<byte[]>
   Summary:
       205 of java.lang.Class (2 unique instances)
       103 of java.lang.String[] (1 elements) (103 unique instances)
       102 of java.lang.String (102 unique instances)
       102 of byte[][] (1 elements) (102 unique instances)  

更新 1:
我做了更多的挖掘,并能够将其缩小到调用 FindClass。这让我更加困惑! :/这是我简化的 native_update_cue_point 方法。我是否需要以某种方式释放 FindClass 调用!?

代码

static void java_update_cue_point (JNIEnv *env, jclass clazz, jobject thiz, int reqType, Cue *pCue)
{
    jclass jstringClass = env->FindClass("java/lang/String");
}

堆栈跟踪

JNI ERROR (app bug): local reference table overflow (max=512)
 local reference table dump:
   Last 10 entries (of 512):
       511: 0x70732980 java.lang.Class<java.lang.String>
       510: 0x70732980 java.lang.Class<java.lang.String>
       509: 0x70732980 java.lang.Class<java.lang.String>
       508: 0x70732980 java.lang.Class<java.lang.String>
       507: 0x70732980 java.lang.Class<java.lang.String>
       506: 0x70732980 java.lang.Class<java.lang.String>
       505: 0x70732980 java.lang.Class<java.lang.String>
       504: 0x70732980 java.lang.Class<java.lang.String>
       503: 0x70732980 java.lang.Class<java.lang.String>
       502: 0x70732980 java.lang.Class<java.lang.String>
   Summary:
       512 of java.lang.Class (1 unique instances)  

更新 2:
我能够通过对每个本地对象执行 DeleteLocalRef 来修复表溢出,而不管 Java VM 是否会处理它。我的想法是虚拟机正在堆栈上累积项目,这些项目将在执行后释放。我需要明确地从堆栈中删除每个项目,以避免在大循环中稍后删除不需要的累积。

我仍然觉得我还没有完全理解为什么会发生这种情况以及为什么我的解决方案可以解决它。

固定代码

static void java_update_cue_point (JNIEnv *env, jclass clazz, jobject thiz, int reqType, Cue *pCue)
{
    if (!pCue) {
        return;
    }

    jclass jstringClass = env->FindClass("java/lang/String");
    jclass jbyteArrayClass = env->FindClass("[B");
    jlong id = pCue->id;
    jint type = pCue->type;
    jint extra = pCue->extra;
    jlong pos = pCue->pos;
    jlong duration = pCue->duration;
    jobjectArray jkeys = NULL;
    jobjectArray jvalues = NULL;

    int dataSize = pCue->data.size();

    if (0 < dataSize)
    {
        jkeys = env->NewObjectArray(dataSize, jstringClass, NULL);
        jvalues = env->NewObjectArray(dataSize, jbyteArrayClass, NULL);

        int position = 0;
        for (std::map<std::string, std::string>::iterator it = pCue->data.begin(); it != pCue->data.end(); it++)
        {
            // set key
            jstring jkey = env->NewStringUTF(it->first.c_str());
            env->SetObjectArrayElement(jkeys, position, jkey);
            env->DeleteLocalRef(jkey); // FIX

            // set value
            jbyteArray byteArray = env->NewByteArray(it->second.length());
            env->SetByteArrayRegion(byteArray, 0, it->second.length(), (const signed char *)it->second.c_str());
            env->SetObjectArrayElement(jvalues, position, byteArray);
            env->DeleteLocalRef(byteArray);

            position++;
        }
    }

    // report update
    env->CallStaticVoidMethod(
                clazz,
                smFields.native_callback_add_cue_point,
                thiz,
                (jint)reqType, id, type, extra, pos, duration, jkeys, jvalues);

    if (jkeys) {
        env->DeleteLocalRef(jkeys); // FIX
    }

    if (jvalues) {
        env->DeleteLocalRef(jvalues); // FIX
    }

    if (jstringClass) {
        env->DeleteLocalRef(jstringClass); // FIX
    }

    if (jbyteArrayClass) {
        env->DeleteLocalRef(jbyteArrayClass); // FIX
    }
}

最佳答案

您正在创建太多本地引用(两个用于类,两个用于对象数组,还有一些用于每次运行 java_update_cue_point 的字符串和字节数组)。 VM 只能处理有限数量的本地引用。有关某些文档,请参阅“Referencing Java Objects”。 JNI 函数文档中的“Global and Local References”部分也有一些详细信息。当 native 方法返回时,VM 将自动释放本地引用,但在像您这样的循环中,您将不得不使用 DeleteLocalRef 删除不再需要的引用。

为了优化一点,我的建议是在 native_update_cue_points 的循环外执行两个 FindClass 调用,并将它们作为参数传递给 java_update_cue_point。这样,当 native_update_cue_points 返回时,VM 将为您释放的类对象只有两个引用,并且您不会一遍又一遍地找到相同的类,从而节省了处理时间。

关于android-ndk - 为什么会出现本地引用表溢出?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34005559/

相关文章:

Android NDK 和 Google Play 过滤

Android NDK C++ 类未定义引用

android - 在 Android NDK 上构建 OpenSSL

android - 从 native 线程获取 Android 上的回溯?

java - 从 c++ (jni) 调用 java 函数根本不起作用

c - GCC 4.0.2 取消引用指向结构的双类型成员的指针会引发 SIGBUS 错误

java - Java 中的链接错误

java - 处理 JNI 崩溃

java - PosixFileFunctions.stat在Linux for Gradle 2.3中失败并显示UnsatisfiedLinkError

没有 C++ 包装器库的 Java native 调用