Search code examples
javareferencejava-native-interface

Why do I need to release global references created in JNI native functions?


I have a Java class with native functions implemented in C++ which is called nz.ac.unitec.BigInteger. The native implementation of nz.ac.unitec.BigInteger is simple, just wraps the java.math.BigInteger and call its constructor and add, subtract, multiply ... functions. A field mNativeContext in nz.ac.unitec.BigInteger is used to store a global reference to a java.math.BigInteger object. The object has a finalizer that should destroy the global reference when the object is garbage collected so I am not leaking global references.

When I runned a simple test loop to create a number of nz.ac.unitec.BigInteger objects without explicitly releasing the created objects, a JNI error was reported as following log (LOG1). But if I release the created objects explicitly before leaving the test function, the stress test can be executed successfully.

Why is the JVM running out of global references when I have a finalizer to delete them when the objects are garbage collected?

LOG1:

F/art     (10730): art/runtime/indirect_reference_table.cc:113] JNI ERROR (app bug): global reference table overflow (max=51200)

F/art     (10730): art/runtime/indirect_reference_table.cc:113] global reference table dump:
F/art     (10730): art/runtime/indirect_reference_table.cc:113]   Last 10 entries (of 51200):
F/art     (10730): art/runtime/indirect_reference_table.cc:113]     51199: 0x12e88790 java.math.BigInteger
F/art     (10730): art/runtime/indirect_reference_table.cc:113]     51198: 0x12e85490 java.math.BigInteger
F/art     (10730): art/runtime/indirect_reference_table.cc:113]     51197: 0x12e81790 java.math.BigInteger
F/art     (10730): art/runtime/indirect_reference_table.cc:113]     51196: 0x12e7e760 java.math.BigInteger
F/art     (10730): art/runtime/indirect_reference_table.cc:113]     51195: 0x12e7ab20 java.math.BigInteger
F/art     (10730): art/runtime/indirect_reference_table.cc:113]     51194: 0x12e77790 java.math.BigInteger
F/art     (10730): art/runtime/indirect_reference_table.cc:113]     51193: 0x12e73a90 java.math.BigInteger
F/art     (10730): art/runtime/indirect_reference_table.cc:113]     51192: 0x12e71af0 java.math.BigInteger
F/art     (10730): art/runtime/indirect_reference_table.cc:113]     51191: 0x12e6dd60 java.math.BigInteger
F/art     (10730): art/runtime/indirect_reference_table.cc:113]     51190: 0x12e6b9a0 java.math.BigInteger
F/art     (10730): art/runtime/indirect_reference_table.cc:113]   Summary:
F/art     (10730): art/runtime/indirect_reference_table.cc:113]      1456 of java.math.BigInteger (1456 unique instances)
F/art     (10730): art/runtime/indirect_reference_table.cc:113]         2 of android.opengl.EGLDisplay (2 unique instances)
F/art     (10730): art/runtime/indirect_reference_table.cc:113]      1889 of java.math.BigInteger (1889 unique instances)
F/art     (10730): art/runtime/indirect_reference_table.cc:113]         1 of java.lang.String
F/art     (10730): art/runtime/indirect_reference_table.cc:113]        27 of java.math.BigInteger (27 unique instances)
F/art     (10730): art/runtime/indirect_reference_table.cc:113]         1 of java.lang.String
F/art     (10730): art/runtime/indirect_reference_table.cc:113]      3771 of java.math.BigInteger (3771 unique instances)
F/art     (10730): art/runtime/indirect_reference_table.cc:113]         1 of dalvik.system.PathClassLoader
F/art     (10730): art/runtime/runtime.cc:284] Runtime aborting...
F/art     (10730): art/runtime/runtime.cc:284] Aborting thread:

Calculator.java

package nz.ac.unitec.calculator;

public class MainActivity extends AppCompatActivity
{
    protected void onCreate(Bundle savedInstanceState)
    {
        Random ra = new Random();
        for(int i=0; i<6000000; ++i) {
            testCreateFinalize(ra);
            int m = ra.nextInt();
            int n = 8;
            int re = m + n;
            Log.i("MainActivity", "re=" + re);
            //BigInteger result = l.subtract(r);
        }

    private void testCreateFinalize(Random ra)
    {
        BigInteger l = new BigInteger("100", 10);

        BigInteger r = new BigInteger("200", 10);
        //l.release();      when adding this two code lines, the test is ok
        //r.release();;
    }
}

BigInteger.java

package nz.ac.unitec.mathutils;
public class BigInteger
{
    static {
        System.loadLibrary("BigInteger_jni");
        native_init();
    }

    private long mNativeContext;

    private BigInteger()
    {
        mNativeContext = 0;
        native_setup();
    }

    public BigInteger(String val, int radix)
    {
        mNativeContext = 0;
        native_setup_bystring(val, radix);
    }

    public void release() {
        native_finalize();
    }

    protected void finalize() {
        native_finalize();
    }


    private static native final void native_init();
    private native final void native_setup();
    private native final void native_setup_bystring(String val, int radix);
    private native final void native_finalize();
    public native String  toString(int radix);


    public native BigInteger add(BigInteger rval);
    public native BigInteger multiply(BigInteger rval);
    public native BigInteger subtract(BigInteger rval);

}

Native Code

static jobject getNativeBigInteger_l(JNIEnv* env, jobject thiz)
{
    return reinterpret_cast<jobject> (env->GetLongField(thiz, fields.context));   //reinterpret_cast<jobject>
}

static void setNativeBigInteger_l(JNIEnv* env, jobject thiz, jobject bi)
{
    env->SetLongField(thiz, fields.context, reinterpret_cast<jlong>(bi)); //reinterpret_cast<jlong>
}

JNIEXPORT void JNICALL Java_nz_ac_unitec_mathutils_BigInteger_native_1setup_1bystring
        (JNIEnv *env, jobject thiz, jstring val, jint radix)
{

    jclass cls = env->FindClass(gBuiltinClassBigInteger);
    ALOGV("-Java_nz_ac_unitec_mathutils_BigInteger_native_1setup_1bystring env->FindClass(%s) return (%0x)",
          gBuiltinClassBigInteger, cls);

    if (cls == NULL) {
        ALOGE("-Java_nz_ac_unitec_mathutils_BigInteger_native_1setup_1bystring env->FindClass(%s) return NULL", gBuiltinClassBigInteger);
        return;
    }

    jmethodID constructor = env->GetMethodID(cls, "<init>", "(Ljava/lang/String;I)V");
    if (constructor == NULL) {
        env->DeleteLocalRef(cls);
        ALOGE("-Java_nz_ac_unitec_mathutils_BigInteger_native_1setup_1bystring env->GetMethodID(%s) return NULL", "<init>");
        return;
    }

    jobject jobj = env->NewObject(cls, constructor, val, radix);
    ALOGV("-Java_nz_ac_unitec_mathutils_BigInteger_native_1setup_1bystring env->NewObject return (%0x)",
          jobj);
    if (NULL == jobj) {
        env->DeleteLocalRef(cls);
        ALOGE("-Java_nz_ac_unitec_mathutils_BigInteger_native_1setup_1bystring env->NewObject return NULL");
        return;
    }

    jobject gjobj = env->NewGlobalRef(jobj);
    ALOGV("-Java_nz_ac_unitec_mathutils_BigInteger_native_1setup_1bystring env->NewGlobalRef return (%0x)", gjobj);
    setNativeBigInteger_l(env, thiz, gjobj);
    env->DeleteLocalRef(jobj);
    //env->DeleteLocalRef(cls);
}

JNIEXPORT void JNICALL
Java_nz_ac_unitec_mathutils_BigInteger_native_1finalize
        (JNIEnv *env, jobject thiz)
{
    ALOGV("+native_finalize");
    jobject obj = getNativeBigInteger_l(env, thiz);
    ALOGV("-Java_nz_ac_unitec_mathutils_BigInteger_native_1finalize getNativeBigInteger_l return (%0x)",
                          obj);
    if (obj == NULL) {
        ALOGE("-native_finalize getNativeBigInteger_l NULL");
        return;
    }
    env->DeleteGlobalRef(obj);
    setNativeBigInteger_l(env, thiz, NULL);
    ALOGV("-native_finalize");
}


static JNINativeMethod gMethods[] = {
        {"native_init",         "()V",
                (void *)Java_nz_ac_unitec_mathutils_BigInteger_native_1init},

        {"native_setup",    "()V",
                (void *)Java_nz_ac_unitec_mathutils_BigInteger_native_1setup},

        {"native_setup_bystring", "(Ljava/lang/String;I)V",
                (void*)Java_nz_ac_unitec_mathutils_BigInteger_native_1setup_1bystring},


        {"native_finalize",     "()V",
                (void *)Java_nz_ac_unitec_mathutils_BigInteger_native_1finalize},

        {"add",     "(Lnz/ac/unitec/mathutils/BigInteger;)Lnz/ac/unitec/mathutils/BigInteger;",
         (void *)Java_nz_ac_unitec_mathutils_BigInteger_add},

        {"multiply","(Lnz/ac/unitec/mathutils/BigInteger;)Lnz/ac/unitec/mathutils/BigInteger;",
                (void *)Java_nz_ac_unitec_mathutils_BigInteger_multiply},
        {"subtract","(Lnz/ac/unitec/mathutils/BigInteger;)Lnz/ac/unitec/mathutils/BigInteger;",
                (void *)Java_nz_ac_unitec_mathutils_BigInteger_subtract},
        {"toString","(I)Ljava/lang/String;",
                (void *)Java_nz_ac_unitec_mathutils_BigInteger_toString}
};

int register_Java_nz_ac_unitec_mathutils_BigInteger(JNIEnv *env)
{
    return jniRegisterNativeMethods(env, gClassBigInteger, gMethods, NELEM(gMethods));
}

Solution

  • Your code is failing because you have too many global references to objects that have not been released back into the memory pool. The global reference table has a maximum size to help you catch memory leaks and prevent your program from running out of memory. The log message you pasted tells you what those objects are: java.math.BigInteger.

    If you look at your implementation of native_setup_bystring you can see that you're creating a global reference to a new BigInteger object:

    jobject gjobj = env->NewGlobalRef(jobs);
    

    Global references are not automatically garbage collected [1][2] so you need to explicitly delete them, which is what you've found in your testing.

    The problem with your approach is that you're storing a reference to the internal object using a direct memory reference into the heap (your long mNativeContext field). This approach is not a good one because you're preventing the the JVM from managing the garbage collection of your BigIntegers. A better approach would be to avoid using a global reference at all, and store an object reference instead of a long. If you do this then the JVM will be able to automatically collect all of the objects you're allocating.