androidc++android-ndkjava-native-interface

I don't know how to solve "local reference table overflow" error in android JNI


I am experiencing a local reference overflow error when running my algorithm written in JNI. The code works fine up to 2000-3000 iterations, but after that, it crashes with the error message "local reference table overflow". I suspect there might be a memory leak somewhere in the code, but I can't seem to find it.

Here is the code I'm using:

#include <valarray>
#include <map>
#include <jni.h>
#include <android/log.h>
#include <thread>
#include <time.h>


#define COULOMB 4800.0
#define DISTANCE 3200
#define GRAVITY 0.04
#define BOUNCE 0.06
#define ATTENUATION 0.4

jboolean op = true;

extern "C" JNIEXPORT void JNICALL
Java_com_example_networkmemo_algorithm_ForcedGraphAlgorithm_operate(
        JNIEnv* env,
        jobject thiz,
        jobject nodes,
        jobject edges,
        jobject nodeId2Index
) {
    jclass com_example_composableoptimizing_Node = static_cast<jclass>(env ->NewGlobalRef(env ->FindClass("com/example/networkmemo/db/Node")));
    jmethodID node_set_dx = env ->GetMethodID(com_example_composableoptimizing_Node, "setDx", "(D)V");
    jmethodID node_set_dy = env ->GetMethodID(com_example_composableoptimizing_Node, "setDy", "(D)V");
    jmethodID node_set_x = env ->GetMethodID(com_example_composableoptimizing_Node, "setX", "(D)V");
    jmethodID node_set_y = env ->GetMethodID(com_example_composableoptimizing_Node, "setY", "(D)V");

    jfieldID node_dx_field = env ->GetFieldID(com_example_composableoptimizing_Node, "dx", "D");
    jfieldID node_dy_field = env ->GetFieldID(com_example_composableoptimizing_Node, "dy", "D");
    jfieldID node_x_field = env ->GetFieldID(com_example_composableoptimizing_Node, "x", "D");
    jfieldID node_y_field = env ->GetFieldID(com_example_composableoptimizing_Node, "y", "D");
    jfieldID node_size_field = env ->GetFieldID(com_example_composableoptimizing_Node, "size", "D");

    jclass com_example_composableoptimizing_Edge = static_cast<jclass>(env ->NewGlobalRef(env ->FindClass("com/example/networkmemo/db/Edge")));
    jfieldID edge_node1_field = env ->GetFieldID(com_example_composableoptimizing_Edge, "node1", "J");
    jfieldID edge_node2_field = env ->GetFieldID(com_example_composableoptimizing_Edge, "node2", "J");

    jclass java_util_ArrayList = static_cast<jclass>(env ->NewGlobalRef(env ->FindClass("java/util/ArrayList")));
    jmethodID java_util_ArrayList_size = env ->GetMethodID(java_util_ArrayList, "size", "()I");
    jmethodID java_util_ArrayList_get = env ->GetMethodID(java_util_ArrayList, "get","(I)Ljava/lang/Object;");
    jmethodID java_util_ArrayList_set = env ->GetMethodID(java_util_ArrayList, "set", "(ILjava/lang/Object;)Ljava/lang/Object;");

    jclass java_util_HashMap = static_cast<jclass>(env ->NewGlobalRef(env ->FindClass("java/util/HashMap")));
    jmethodID java_util_HashMap_get = env ->GetMethodID(java_util_HashMap, "get","(Ljava/lang/Object;)Ljava/lang/Object;");

    jclass java_lang_Integer = static_cast<jclass>(env ->NewGlobalRef(env ->FindClass("java/lang/Integer")));
    jmethodID java_lang_Integer_parseInt = env ->GetStaticMethodID(java_lang_Integer, "parseInt", "(Ljava/lang/String;)I");
    // jmethodID java_lang_Integer_valueOf = env ->GetStaticMethodID(java_lang_Integer, "valueOf", "(I)Ljava/lang/Integer;");

    jclass java_lang_Long = static_cast<jclass>(env ->NewGlobalRef(env ->FindClass("java/lang/Long")));
    // jmethodID java_lang_Long_parseLong = env ->GetStaticMethodID(java_lang_Long, "parseLong", "(Ljava/lang/String;)J");
    jmethodID java_lang_Long_valueOf = env ->GetStaticMethodID(java_lang_Long, "valueOf", "(J)Ljava/lang/Long;");

    jclass java_lang_Double = static_cast<jclass>(env ->NewGlobalRef(env ->FindClass("java/lang/Double")));
    // jmethodID java_lang_Double_valueOf = env ->GetStaticMethodID(java_lang_Double, "valueOf", "(D)Ljava/lang/Double;");

    jclass java_lang_Object = static_cast<jclass>(env ->NewGlobalRef(env ->FindClass("java/lang/Object")));
    jmethodID java_lang_Object_toString = env ->GetMethodID(java_lang_Object, "toString", "()Ljava/lang/String;");

    jint nodeLen = env ->CallIntMethod(nodes, java_util_ArrayList_size);
    jint edgeLen = env ->CallIntMethod(edges, java_util_ArrayList_size);

    jobject* ithNode = (jobject*) malloc(4);
    jobject* jthNode = (jobject*) malloc(4);
    jobject* jthEdge = (jobject*) malloc(4);
    jobject* jthNode1Object = (jobject*) malloc(4);
    jobject* jthNode2Object = (jobject*) malloc(4);
    jobject* jthNode1IndexObject = (jobject*) malloc(4);
    jobject* jthNode2IndexObject = (jobject*) malloc(4);
    jstring* jthNode1IndexString = (jstring*) malloc(4);
    jstring* jthNode2IndexString = (jstring*) malloc(4);
    jobject* targetNode = (jobject*) malloc(4);

    jdouble* ithX = (jdouble*) malloc(4);
    jdouble* ithY = (jdouble*) malloc(4);
    jdouble* ithDx = (jdouble*) malloc(4);
    jdouble* ithDy = (jdouble*) malloc(4);
    jdouble* ithSize = (jdouble*) malloc(4);

    jdouble* fx = (jdouble*) malloc(4);
    jdouble* fy = (jdouble*) malloc(4);

    jdouble* jthX = (jdouble*) malloc(4);
    jdouble* jthY = (jdouble*) malloc(4);

    jdouble* distX = (jdouble*) malloc(4);
    jdouble* distY = (jdouble*) malloc(4);
    jdouble* rsq = (jdouble*) malloc(4);
    jdouble* rsqRound = (jdouble*) malloc(4);

    jdouble* coulombDistX = (jdouble*) malloc(4);
    jdouble* coulombDistY = (jdouble*) malloc(4);
    jdouble* coulombDistRoundX = (jdouble*) malloc(4);
    jdouble* coulombDistRoundY = (jdouble*) malloc(4);

    jdouble* distXC = (jdouble*) malloc(4);
    jdouble* distYC = (jdouble*) malloc(4);

    jlong* jthNode1 = (jlong*) malloc(4);
    jlong* jthNode2 = (jlong*) malloc(4);

    jint* jthNode1Index = (jint*) malloc(4);
    jint* jthNode2Index = (jint*) malloc(4);

    jdouble* targetNodeX = (jdouble*) malloc(4);
    jdouble* targetNodeY = (jdouble*) malloc(4);

    jint* i = (jint*) malloc(4);
    jint* j = (jint*) malloc(4);

    clock_t start, end, result;

    while(op) {

        start = clock();

        for (*i = 0; *i < nodeLen; (*i)++) {
            *ithNode = env->CallObjectMethod(nodes, java_util_ArrayList_get, *i);
            *ithX = env->GetDoubleField(*ithNode, node_x_field);
            *ithY = env->GetDoubleField(*ithNode, node_y_field);
            *ithDx = env->GetDoubleField(*ithNode, node_dx_field);
            *ithDy = env->GetDoubleField(*ithNode, node_dy_field);
            *ithSize = env->GetDoubleField(*ithNode, node_size_field);

            *fx = 0.0;
            *fy = 0.0;

            for (*j = 0; *j < nodeLen; (*j)++) {
                *jthNode = env->CallObjectMethod(nodes, java_util_ArrayList_get, *j);
                *jthX = env->GetDoubleField(*jthNode, node_x_field);
                *jthY = env->GetDoubleField(*jthNode, node_y_field);

                *distX = (*ithX + *ithSize / 2) - (*jthX + *ithSize / 2);
                *distY = (*ithY + *ithSize / 2) - (*jthY + *ithSize / 2);
                *rsq = *distX * *distX + *distY * *distY;
                *rsqRound = (jint) *rsq * 100;
                *rsq = (jdouble) (*rsqRound / 100);

                *coulombDistX = COULOMB * *distX;
                *coulombDistY = COULOMB * *distY;
                *coulombDistRoundX = (jint) *coulombDistX * 100;
                *coulombDistRoundY = (jint) *coulombDistY * 100;
                *coulombDistX = (jdouble) (*coulombDistRoundX / 100);
                *coulombDistY = (jdouble) (*coulombDistRoundY / 100);

                if (*rsq != 0.0 && sqrt(*rsq) < DISTANCE) {
                    *fx += *coulombDistX / *rsq;
                    *fy += *coulombDistY / *rsq;
                }
            }

            *distXC = -1 * (*ithX + *ithSize / 2);
            *distYC = -1 * (*ithY + *ithSize / 2);
            *fx += GRAVITY * *distXC;
            *fy += GRAVITY * *distYC;

            for (*j = 0; *j < edgeLen; (*j)++) {

                *jthEdge = env->CallObjectMethod(edges, java_util_ArrayList_get, *j);
                *jthNode1 = env->GetLongField(*jthEdge, edge_node1_field);
                *jthNode2 = env->GetLongField(*jthEdge, edge_node2_field);

                *jthNode1Object = env->CallStaticObjectMethod(java_lang_Long,
                                                              java_lang_Long_valueOf, *jthNode1);
                *jthNode2Object = env->CallStaticObjectMethod(java_lang_Long,
                                                              java_lang_Long_valueOf, *jthNode2);

                *jthNode1IndexObject = env->CallObjectMethod(nodeId2Index, java_util_HashMap_get,
                                                             *jthNode1Object);
                *jthNode2IndexObject = env->CallObjectMethod(nodeId2Index, java_util_HashMap_get,
                                                             *jthNode2Object);

                *jthNode1IndexString = static_cast<jstring>(env->CallObjectMethod(
                        *jthNode1IndexObject, java_lang_Object_toString));
                *jthNode2IndexString = static_cast<jstring>(env->CallObjectMethod(
                        *jthNode2IndexObject, java_lang_Object_toString));

                *jthNode1Index = env->CallStaticIntMethod(java_lang_Integer,
                                                              java_lang_Integer_parseInt,
                                                              *jthNode1IndexString);
                *jthNode2Index = env->CallStaticIntMethod(java_lang_Integer,
                                                              java_lang_Integer_parseInt,
                                                              *jthNode2IndexString);

                *distX = 0.0;
                *distY = 0.0;
                if (*i == *jthNode1Index) {
                    *targetNode = env->CallObjectMethod(nodes, java_util_ArrayList_get,
                                                        *jthNode2Index);
                    *targetNodeX = env->GetDoubleField(*targetNode, node_x_field);
                    *targetNodeY = env->GetDoubleField(*targetNode, node_y_field);

                    *distX = *targetNodeX - *ithX;
                    *distY = *targetNodeY - *ithY;
                } else if (*i == *jthNode2Index) {
                    *targetNode = env->CallObjectMethod(nodes, java_util_ArrayList_get,
                                                        *jthNode1Index);
                    *targetNodeX = env->GetDoubleField(*targetNode, node_x_field);
                    *targetNodeY = env->GetDoubleField(*targetNode, node_y_field);

                    *distX = *targetNodeX - *ithX;
                    *distY = *targetNodeY - *ithY;
                }

                *fx += BOUNCE * *distX;
                *fy += BOUNCE * *distY;
            }

            *ithDx = (*ithDx + *fx) * ATTENUATION;
            *ithDy = (*ithDy + *fy) * ATTENUATION;

            env->CallVoidMethod(*ithNode, node_set_dx, *ithDx);
            env->CallVoidMethod(*ithNode, node_set_dy, *ithDy);
            env->CallVoidMethod(*ithNode, node_set_x, *ithX + *ithDx);
            env->CallVoidMethod(*ithNode, node_set_y, *ithY + *ithDy);

            env->CallObjectMethod(nodes, java_util_ArrayList_set, *i, *ithNode);
        }
        end = clock();
        result = end - start;
        __android_log_print(ANDROID_LOG_VERBOSE, "APPNAME", "%ld", result);
        if(result < 30) {
            std::this_thread::sleep_for(std::chrono::milliseconds(30 - result));
        }
    }

    free(ithNode);
    free(jthNode);
    free(jthEdge);
    free(jthNode1Object);
    free(jthNode2Object);
    free(jthNode1IndexObject);
    free(jthNode2IndexObject);
    free(jthNode1IndexString);
    free(jthNode2IndexString);
    free(targetNode);

    free(ithX);
    free(ithY);
    free(ithDx);
    free(ithDy);
    free(ithSize);

    free(fx);
    free(fy);
    free(jthX);
    free(jthY);
    free(distX);
    free(distY);

    free(rsq);
    free(rsqRound);
    free(coulombDistX);
    free(coulombDistY);
    free(coulombDistRoundX);
    free(coulombDistRoundY);

    free(distXC);
    free(distYC);
    free(jthNode1);
    free(jthNode2);
    free(jthNode1Index);
    free(jthNode2Index);
    free(targetNodeX);
    free(targetNodeY);

    free(i);
    free(j);

    env ->DeleteGlobalRef(com_example_composableoptimizing_Node);
    env ->DeleteGlobalRef(com_example_composableoptimizing_Edge);
    env ->DeleteGlobalRef(java_util_ArrayList);
    env ->DeleteGlobalRef(java_util_HashMap);
    env ->DeleteGlobalRef(java_lang_Integer);
    env ->DeleteGlobalRef(java_lang_Long);
    env ->DeleteGlobalRef(java_lang_Double);
    env ->DeleteGlobalRef(java_lang_Object);
}

I would appreciate any help in identifying the cause of the memory leak and resolving the local reference overflow error.

Thank you in advance for your assistance.

There is an error message.

2024-03-18 17:31:27.129 25373-25397 ple.networkmem          com.example.networkmemo              A  jni_env_ext-inl.h:32] JNI ERROR (app bug): local reference table overflow (max=8388608)
                                                                                                    jni_env_ext-inl.h:32] local reference table dump:
                                                                                                    jni_env_ext-inl.h:32]   Last 10 entries (of 8388608):
                                                                                                    jni_env_ext-inl.h:32]     8388607: 0x134024d8 com.example.networkmemo.db.Node
                                                                                                    jni_env_ext-inl.h:32]     8388606: 0x13402458 com.example.networkmemo.db.Node
                                                                                                    jni_env_ext-inl.h:32]     8388605: 0x134023d8 com.example.networkmemo.db.Node
                                                                                                    jni_env_ext-inl.h:32]     8388604: 0x13402358 com.example.networkmemo.db.Node
                                                                                                    jni_env_ext-inl.h:32]     8388603: 0x134022d8 com.example.networkmemo.db.Node
                                                                                                    jni_env_ext-inl.h:32]     8388602: 0x13402258 com.example.networkmemo.db.Node
                                                                                                    jni_env_ext-inl.h:32]     8388601: 0x134021d8 com.example.networkmemo.db.Node
                                                                                                    jni_env_ext-inl.h:32]     8388600: 0x13402158 com.example.networkmemo.db.Node
                                                                                                    jni_env_ext-inl.h:32]     8388599: 0x134020d8 com.example.networkmemo.db.Node
                                                                                                    jni_env_ext-inl.h:32]     8388598: 0x13402058 com.example.networkmemo.db.Node
                                                                                                    jni_env_ext-inl.h:32]   Summary:
                                                                                                    jni_env_ext-inl.h:32]     8388599 of com.example.networkmemo.db.Node (30 unique instances)
                                                                                                    jni_env_ext-inl.h:32]         8 of java.lang.Class (8 unique instances)
                                                                                                    jni_env_ext-inl.h:32]         1 of kotlinx.coroutines.scheduling.CoroutineScheduler$Worker
                                                                                                    jni_env_ext-inl.h:32]  Resizing failed: Requested size exceeds maximum: 16777216
2024-03-18 17:31:27.172 25373-25397 ple.networkmem          com.example.networkmemo              A  runtime.cc:669] Runtime aborting...
                                                                                                    runtime.cc:669] All threads:
                                                                                                    runtime.cc:669] DALVIK THREADS (23):
                                                                                                    runtime.cc:669] "DefaultDispatcher-worker-2" prio=5 tid=22 Runnable
                                                                                                    runtime.cc:669]   | group="" sCount=0 ucsCount=0 flags=0 obj=0x13401590 self=0xb4000070373bc520
                                                                                                    runtime.cc:669]   | sysTid=25397 nice=0 cgrp=top-app sched=0/0 handle=0x6e492afcb0
                                                                                                    runtime.cc:669]   | state=R schedstat=( 7673982074 795384995 686 ) utm=664 stm=102 core=2 HZ=100
                                                                                                    runtime.cc:669]   | stack=0x6e491ac000-0x6e491ae000 stackSize=1039KB

Thank you for your attention. The reason I allocated all of them is because I tried various things to solve the error, but it didn't work...


Solution

  • When the JVM invokes a native function, it allocates a table for local object references. These are needed to inform the garbage collector which objects your native code still references. At the end of your function these references are released and the GC can potentially clean up after you.

    This local reference table is not infinitely large and you ran into its limits. You thus need to do some housekeeping yourself by using DeleteLocalRef. This tells the JVM you are done with an object and it can reuse the slot for another object reference.

    Stripping your code down to just the object allocations, you need to add a DeleteLocalRef every time you allocate an object or retrieve an object reference inside a loop:

    for (*i = 0; *i < nodeLen; (*i)++) {
            *ithNode = env->CallObjectMethod(nodes, java_util_ArrayList_get, *i);
    
            /* ... */
    
            for (*j = 0; *j < nodeLen; (*j)++) {
                    *jthNode = env->CallObjectMethod(nodes, java_util_ArrayList_get, *j);
    
                    /* ... */
                    env->DeleteLocalRef(*jthNode);
            }
    
            for (*j = 0; *j < edgeLen; (*j)++) {
    
                    *jthEdge = env->CallObjectMethod(edges, java_util_ArrayList_get, *j);
    
                    *jthNode1Object = env->CallStaticObjectMethod(java_lang_Long, java_lang_Long_valueOf, *jthNode1);
                    *jthNode2Object = env->CallStaticObjectMethod(java_lang_Long, java_lang_Long_valueOf, *jthNode2);
    
                    *jthNode1IndexObject = env->CallObjectMethod(nodeId2Index, java_util_HashMap_get, *jthNode1Object);
                    *jthNode2IndexObject = env->CallObjectMethod(nodeId2Index, java_util_HashMap_get, *jthNode2Object);
    
                    *jthNode1IndexString = static_cast<jstring>(env->CallObjectMethod( *jthNode1IndexObject, java_lang_Object_toString));
                    *jthNode2IndexString = static_cast<jstring>(env->CallObjectMethod( *jthNode2IndexObject, java_lang_Object_toString));
    
                    /* ... */
                    if (*i == *jthNode1Index) {
                            *targetNode = env->CallObjectMethod(nodes, java_util_ArrayList_get, *jthNode2Index);
                            /* ... */
                            env->DeleteLocalRef(*targetNode);
                    } else if (*i == *jthNode2Index) {
                            *targetNode = env->CallObjectMethod(nodes, java_util_ArrayList_get, *jthNode1Index);
                            /* ... */
                            env->DeleteLocalRef(*targetNode);
                    }
    
                    env->DeleteLocalRef(*jthEdge);
                    env->DeleteLocalRef(*jthNode1Object);
                    env->DeleteLocalRef(*jthNode2Object);
                    env->DeleteLocalRef(*jthNode1IndexObject);
                    env->DeleteLocalRef(*jthNode2IndexObject);
                    env->DeleteLocalRef(*jthNode1IndexString);
                    env->DeleteLocalRef(*jthNode2IndexString);
            }
    
            env->CallObjectMethod(nodes, java_util_ArrayList_set, *i, *ithNode);
            env->DeleteLocalRef(*ithNode);
    }