Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
225 views
in Technique[技术] by (71.8m points)

java - 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));
}
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...