11

まず、JNA と Java がネイティブ メモリの割り当てを指示する方法についての私の理解はせいぜい本能的なものであり、何が起こっているのかについての私の理解を説明しようとしています。応答に加えて、修正は素晴らしいでしょう...

JNA を使用して Java と C のネイティブ コードを混在させるアプリケーションを実行していますが、Java ガベージ コレクターが直接のネイティブ メモリ割り当てへの参照を解放できず、C ヒープがメモリ不足になるという再現可能な問題が発生しています。

java.nio.ByteBufferC コードに a を渡し、バッファを変更してから、Java 関数で結果にアクセスしているため、C アプリケーションが割り当ての問題の原因ではないことは確かです。各関数呼び出し中に単一mallocと単一の対応がありますが、Java でコードを繰り返し実行した後、malloc は最終的に失敗します。free

これは、問題を示すやや単純化されたコード セットです。実際には、関数呼び出し中に C ヒープに約 16 ~ 32MB を割り当てようとしています

私のJavaコードは次のようなことをします:

public class MyClass{
    public void myfunction(){
        ByteBuffer foo = ByteBuffer.allocateDirect(1000000);
        MyDirectAccessLib.someOp(foo, 1000000);
        System.out.println(foo.get(0));
    }
}

public MyDirectAccessLib{
    static {
        Native.register("libsomelibrary");
    }
    public static native void someOp(ByteBuffer buf, int size);
}

次に、私のCコードは次のようになります。

#include <stdio.h>
#include <stdlib.h>
void someOp(unsigned char* buf, int size){
    unsigned char *foo;
    foo = malloc(1000000);
    if(!foo){
        fprintf(stderr, "Failed to malloc 1000000 bytes of memory\n");
        return;
    }
    free(foo);

    buf[0] = 100;
}

問題は、この関数を繰り返し呼び出した後、Java ヒープがある程度安定している (成長が遅い) ことですが、最終的に C 関数はそれ以上メモリを割り当てることができなくなります。大まかに言えば、これは Java が C ヒープにメモリを割り当てているが、Java ByteBuffer オブジェクトが比較的小さいため、このメモリを指す ByteBuffer をクリーンアップしていないためだと思います。

これまでのところ、自分の関数で GC を手動で実行すると、必要なクリーンアップが提供されることがわかりましたが、これはお粗末なアイデアと貧弱なソリューションの両方のようです。

ByteBuffer 領域が適切に解放され、C ヒープ領域が制御されるように、この問題をより適切に管理するにはどうすればよいですか?

問題に対する私の理解は間違っていますか (不適切に実行しているものがありますか)?

編集:実際のアプリケーションをより反映するようにバッファサイズを調整しました。画像には約3000x2000を割り当てています...

4

5 に答える 5

9

あなたは実際にJava VM の既知のバグに直面しています。バグ レポートに記載されている最善の回避策は次のとおりです。

  • -XX:MaxDirectMemorySize= オプションを使用して、使用される直接メモリの量を制限できます。この制限を超えるような直接メモリを割り当てようとすると、フル GC が発生し、参照処理が引き起こされ、参照されていないバッファが解放されます。 "

その他の可能な回避策は次のとおりです。

  • 明示的な System.gc() 呼び出しを時々挿入して、ダイレクト バッファーが確実に回収されるようにします。
  • 若い世代のサイズを小さくして、より頻繁な GC を強制します。
  • アプリケーション レベルでダイレクト バッファーを明示的にプールします。

本当にダイレクト バイト バッファに依存したい場合は、アプリケーション レベルでプールすることをお勧めします。アプリケーションの複雑さによっては、単純に同じバッファーをキャッシュして再利用することもできます (複数のスレッドに注意してください)。

于 2009-11-21T13:50:49.737 に答える
4

適切に診断されたと思います。Java ヒープが不足することはないため、JVM はガベージ コレクションを行わず、マップされたバッファーは解放されません。GC を手動で実行しても問題がないという事実は、これを裏付けているようです。二次的な確認として、詳細なコレクション ログをオンにすることもできます。

それで、あなたは何ができますか?まず最初に試したいのは、-Xms コマンドライン引数を使用して、初期 JVM ヒープ サイズを小さく保つことです。これは、GC をより頻繁に実行するため、プログラムが常に Java ヒープに少量のメモリを割り当てている場合に問題を引き起こす可能性があります。

また、pmapツール (または Windows で同等のもの) を使用して、仮想メモリ マップを調べます。可変サイズのバッファーを割り当てることで、C ヒープを断片化している可能性があります。その場合、「アノン」ブロック間にギャップがある、すべての大きな仮想マップが表示されます。その解決策は、必要以上に大きな固定サイズのブロックを割り当てることです。

于 2009-11-16T20:21:52.673 に答える
1

ヒープメモリが不足すると、GCが自動的にトリガーされます。ただし、ダイレクトメモリが不足すると、GCはトリガーされず(少なくともSunのJVMでは)、GCが十分なメモリを解放したとしても、OutOfMemoryErrorが発生します。この状況では、GCを手動でトリガーする必要があることがわかりました。

より良い解決策は、同じByteBufferを再利用して、ByteBufferを再割り当てする必要がないようにすることです。

于 2009-11-21T14:38:42.963 に答える
1

directBuffer[ 1 ]メモリを解放するには、 JNIを使用できます。

JNI 6 APIの関数GetDirectBufferAddress(JNIEnv* env, jobject buf)[ 3 ]を使用してメモリへのポインタを取得し、そのポインタに対して標準コマンドを使用してメモリを解放できます。Bufferfree(void *ptr)

C などのコードを記述して Java から上記の関数を呼び出すのではなく、 JNA[ 6 ]を使用できます。Native.getDirectBufferPointer(Buffer)

その後に残された唯一のことは、Bufferオブジェクトへのすべての参照を放棄することです。Java のガベージ コレクションは、Buffer参照されていない他のオブジェクトと同様に、インスタンスを解放します。

Bufferdirectは、割り当てられたメモリ領域に必ずしも 1:1 でマップするとは限らないことに注意してください。たとえば、JNI API にはNewDirectByteBuffer(JNIEnv* env, void* address, jlong capacity)[ 7 ]があります。Bufferそのため、メモリ割り当て領域がネイティブ メモリと 1 対 1 であることがわかっている のメモリのみを解放する必要があります。

また、上記とまったく同じ理由で、BufferJava のByteBuffer.allocateDirect(int)[ 8 ]によって作成されたダイレクトを解放できるかどうかもわかりません。JVM または Java プラットフォームの実装固有の詳細は、新しい direct を渡すときにプールを使用するか、1:1Bufferのメモリ割り当てを行うかによって異なります。

ByteBufferこれは、直接[ 9 ]処理に関して私のライブラリからわずかに変更されたスニペットに従います(JNA Native[ 10 ]およびPointer[ 11 ]クラスを使用します)。

/**
 * Allocate native memory and associate direct {@link ByteBuffer} with it.
 * 
 * @param bytes - How many bytes of memory to allocate for the buffer
 * @return The created {@link ByteBuffer}.
 */
public static ByteBuffer allocateByteBuffer(int bytes) {
        long lPtr = Native.malloc(bytes);
        if (lPtr == 0) throw new Error(
            "Failed to allocate direct byte buffer memory");
        return Native.getDirectByteBuffer(lPtr, bytes);
}

/**
 * Free native memory inside {@link Buffer}.
 * <p>
 * Use only buffers whose memory region you know to match one to one
 * with that of the underlying allocated memory region.
 * 
 * @param buffer - Buffer whose native memory is to be freed.
 * The class instance will remain. Don't use it anymore.
 */
public static void freeNativeBufferMemory(Buffer buffer) {
        buffer.clear();
        Pointer javaPointer = Native.getDirectBufferPointer(buffer);
        long lPtr = Pointer.nativeValue(javaPointer);
        Native.free(lPtr);
}
于 2012-07-03T22:00:17.403 に答える
1

あなたの問題は、ダイレクトバイトバッファの使用が原因であると思われます。Java ヒープの外側に割り当てることができます。

メソッドを頻繁に呼び出し、毎回小さなバッファーを割り当てる場合、その使用パターンはおそらくダイレクト バッファーには適していません。

問題を特定するために、(Java) ヒープ割り当てバッファーに切り替えます (allocate代わりにメソッドを使用するだけですallocateDirect。それでメモリの問題が解決する場合は、原因が見つかりました。次の質問はダイレクトバイト バッファにパフォーマンス上の利点があるかどうか. そうでない場合 (そして、そうではないと私は推測します)、適切にクリーンアップする方法について心配する必要はありません。

于 2009-11-16T20:19:05.987 に答える