11

Android では、System.gc() を呼び出した場合でも、直接 ByteBuffer がメモリを解放するようには見えません。

例:やっている

Log.v("?", Long.toString(Debug.getNativeHeapAllocatedSize()));
ByteBuffer buffer = allocateDirect(LARGE_NUMBER);
buffer=null;
System.gc();
Log.v("?", Long.toString(Debug.getNativeHeapAllocatedSize()));

は、ログに 2 つの数値を示します。2 番目の数値は、最初の数値よりも少なくとも LARGE_NUMBER 大きくなります。

どうすればこの漏れを取り除くことができますか?


追加した:

C++ 側で alloc/free を処理するという Gregory の提案に従って、次に定義しました。

JNIEXPORT jobject JNICALL Java_com_foo_bar_allocNative(JNIEnv* env, jlong size)
    {
    void* buffer = malloc(size);
    jobject directBuffer = env->NewDirectByteBuffer(buffer, size);
    jobject globalRef = env->NewGlobalRef(directBuffer);
    return globalRef;
    }

JNIEXPORT void JNICALL Java_com_foo_bar_freeNative(JNIEnv* env, jobject globalRef)
    {
    void *buffer = env->GetDirectBufferAddress(globalRef);
    free(buffer);
    env->DeleteGlobalRef(globalRef);
    }

次に、JAVA側でByteBufferを取得します

ByteBuffer myBuf = allocNative(LARGE_NUMBER);

そしてそれを解放します

freeNative(myBuf);

残念ながら、うまく割り当てられますが、a) に従ってメモリが割り当てられたままになりDebug.getNativeHeapAllocatedSize()、b) エラーが発生します。

W/dalvikvm(26733): JNI: DeleteGlobalRef(0x462b05a0) failed to find entry (valid=1)

私は今、完全に混乱しています。少なくとも C++ 側のことは理解していると思っていました... なぜ free() はメモリを返さないのですか? そして、私は何が間違っていDeleteGlobalRef()ますか?

4

4 に答える 4

23

漏れはありません。

ByteBuffer.allocateDirect()malloc()インスタンスにラップされるネイティブ ヒープ/フリー ストア (考える) からメモリを割り当てByteBufferます。

ByteBufferインスタンスがガベージ コレクションされると、ネイティブ メモリが再利用されます (そうしないと、ネイティブ メモリがリークします)。

System.gc()ネイティブ メモリがすぐに回収されることを期待して呼び出しています。ただし、呼び出しSystem.gc()は、2 番目のログ ステートメントがメモリが解放されたことを通知しない理由を説明する要求にすぎません。それは、まだ解放されていないためです!

あなたの状況では、明らかに Java ヒープに十分な空きメモリがあり、ガベージ コレクターは何もしないことを決定します。その結果、到達できないByteBufferインスタンスはまだ収集されず、ファイナライザーは実行されず、ネイティブ メモリは解放されません。

また、JVM のこのバグ(ただし、Dalvik にどのように適用されるかは不明) に注意してください。直接バッファーの大量の割り当てが unrecoverable につながりOutOfMemoryErrorます。


JNIから制御することについてコメントしました。これは実際に可能です。次のように実装できます。

  1. native ByteBuffer allocateNative(long size)次のようなエントリ ポイントを公開します。

    • void* buffer = malloc(size)ネイティブ メモリを割り当てる呼び出し
    • を呼び出して、新しく割り当てられた配列をByteBufferインスタンスにラップします(*env)->NewDirectByteBuffer(env, buffer, size);
    • ByteBufferローカル参照をグローバル参照に変換します(*env)->NewGlobalRef(env, directBuffer);
  2. native void disposeNative(ByteBuffer buffer)次のようなエントリ ポイントを公開します。

    • free()によって返されたダイレクト バッファ アドレスの呼び出し*(env)->GetDirectBufferAddress(env, directBuffer);
    • でグローバル参照を削除します(*env)->DeleteGlobalRef(env, directBuffer);

バッファを呼び出すdisposeNativeと、その参照をもう使用することは想定されていないため、エラーが発生しやすくなります。割り当てパターンに対してそのような明示的な制御が本当に必要かどうかを再検討してください。


グローバル参照について言ったことは忘れてください。実際、グローバル参照は、JNI メソッドへのさらなる呼び出しがその参照を使用できるように、ネイティブ コード (グローバル変数など) に参照を格納する方法です。したがって、たとえば次のようになります。

  • Java からfoo()、ローカル参照 (ネイティブ側からオブジェクトを作成することによって取得) からグローバル参照を作成するネイティブ メソッドを呼び出し、それをネイティブ グローバル変数に (としてjobject)格納します。
  • もう一度 Java から、格納されたbar()データを取得してさらに処理するネイティブ メソッドを呼び出します。jobjectfoo()
  • 最後に、やはり Java から、ネイティブへの最後の呼び出しbaz()でグローバル参照が削除されます

混乱させて申し訳ありません。

于 2011-02-20T23:02:00.140 に答える
1

Android 4.0.3 エミュレーター (Ice Cream Sandwich) でテストするまで、TurqMage のソリューションを使用していました。何らかの理由で、DeleteGlobalRef の呼び出しが次の jni 警告で失敗します。

NewGlobalRef と DeleteGlobalRef (以下を参照) を作成するための呼び出しを取り出したところ、Android 4.0.3 エミュレーターで正常に動作するようです。とにかくそれへのJava参照を保持する必要があるので、そもそも NewGlobalRef() への呼び出しは必要なかったと思います..

JNIEXPORT jobject JNICALL Java_com_foo_allocNativeBuffer(JNIEnv* env, jobject thiz, jlong size)
{
    void* buffer = malloc(size);
    jobject directBuffer = env->NewDirectByteBuffer(buffer, size);
    return directBuffer;
}

JNIEXPORT void JNICALL Java_comfoo_freeNativeBuffer(JNIEnv* env, jobject thiz, jobject bufferRef)
{
    void *buffer = env->GetDirectBufferAddress(bufferRef);

    free(buffer);
}
于 2012-03-27T14:08:41.950 に答える
0

リフレクションを使用してjava.nio.DirectByteBuffer.free()を呼び出します。Android DVM は、上記の方法をサポートする Apache Harmony に触発されていることを思い出してください。

ダイレクト NIO バッファーは、ガベージ コレクションによって管理される Java ヒープではなく、ネイティブ ヒープに割り当てられます。ネイティブ メモリを解放するのは開発者次第です。OpenJDK と Oracle Java では、直接 NIO バッファーの作成が失敗したときにガベージ コレクターを呼び出そうとするため、少し異なりますが、それが役立つという保証はありません。

注意: asFloatBuffer()、asIntBuffer() などを使用する場合は、もう少しいじる必要があります。これは、ダイレクト バイト バッファのみを「解放」できるためです。

于 2015-09-15T13:15:05.200 に答える
0

あなたの最後のコメントが古いのか、それとも何のカスパーなのかわかりません。私は次のことをしました...

JNIEXPORT jobject JNICALL Java_com_foo_allocNativeBuffer(JNIEnv* env, jobject thiz, jlong size)
{
    void* buffer = malloc(size);
    jobject directBuffer = env->NewDirectByteBuffer(buffer, size);
    jobject globalRef = env->NewGlobalRef(directBuffer);

    return globalRef;
}

JNIEXPORT void JNICALL Java_comfoo_freeNativeBuffer(JNIEnv* env, jobject thiz, jobject globalRef)
{
    void *buffer = env->GetDirectBufferAddress(globalRef);

    env->DeleteGlobalRef(globalRef);
    free(buffer);
}

それからJavaで...

mImageData = (ByteBuffer)allocNativeBuffer( mResX * mResY * mBPP );

freeNativeBuffer(mImageData);
mImageData = null;

そして、すべてがうまくいっているようです。このアイデアをくれたグレゴリーに感謝します。JVM で参照されているバグへのリンクが無効になっています。

于 2011-03-14T22:53:28.617 に答える