6

JNI メソッドを使用して Java オブジェクトを構築している場合、JNI 呼び出し API を使用して呼び出している Java メソッドにパラメータとして渡すために、そのメモリをどのように管理すればよいですか?

これが私が取り組んでいるものです:

より複雑なデストラクタ メソッドを持つ C オブジェクトがありますfree()。この C オブジェクトは Java オブジェクトに関連付けられます。アプリケーションが Java オブジェクトで終了すると、C オブジェクトはもう必要ありません。

私は次のようにJavaオブジェクトを作成しています(明確にするためにエラーチェックを省略しています):

c_object = c_object_create ();
class = (*env)->FindClass (env, "my.class.name");
constructor = (*env)->GetMethodID (env, class, "<init>", "(J)V");
instance = (*env)->NewObject (env, class, constructor, (jlong) c_object);

method = (*env)->GetMethodID (env, other_class, "doSomeWork", "(Lmy.class.name)V");
(*env)->CallVoidMethod (env, other_class, method, instance);

では、これで作業が完了したinstanceので、何をすればよいでしょうか? 理想的には、ガベージ コレクションを VM に任せたいと思います。それが完了したら、私が提供したポインターinstanceも呼び出すと素晴らしいでしょう。c_object_destroy()これは可能ですか?

別の関連する質問は、このようなメソッドで作成する Java エンティティのスコープに関係しています。class、、、constructorまたはそれ以上を手動でリリースする必要がmethodありますか? JNI のドキュメントは、適切なメモリ管理に関して、イライラするほど曖昧です (私の判断では)。

4

4 に答える 4

10

JNI仕様は、ここでJNIメソッドで作成されたJavaオブジェクトを誰が「所有」するかという問題をカバーしています。ローカル参照とグローバル参照を区別する必要があります。

JVMがネイティブコードに対してJNI呼び出しを行うと、呼び出し中に作成されたすべてのオブジェクトを追跡するためのレジストリが設定されます。ネイティブ呼び出し中に作成された(つまり、JNIインターフェース関数から返された)オブジェクトはすべて、このレジストリーに追加されます。このようなオブジェクトへの参照は、ローカル参照と呼ばれます。ネイティブメソッドがJVMに戻ると、ネイティブメソッドの呼び出し中に作成されたすべてのローカル参照が破棄されます。ネイティブメソッドの呼び出し中にJVMにコールバックする場合、コントロールがネイティブメソッドに戻ったときに、ローカル参照は引き続き有効です。ネイティブコードから呼び出されたJVMがネイティブコードに別の呼び出しを行うと、ローカル参照の新しいレジストリが作成され、同じルールが適用されます。

(実際、JVMを作成し(これによりJNIEnv *ポインターを受け取ります)、コマンドラインで指定されたクラスを検索し、を呼び出すことにより、JNIインターフェイスを使用して独自のJVM実行可能ファイル(つまりjava.exe)を実装できます。その上でmain()メソッド。)

JNIインターフェースメソッドから返されるすべての参照はローカルです。これは、通常の状況では、JVMに戻るときに参照が破棄されるため、JNIメソッドによって返される参照の割り当てを手動で解除する必要がないことを意味します。たとえば、JVMに戻る前に削除したいローカル参照がたくさんある場合など、「時期尚早に」それらを破棄したい場合があります。

グローバル参照は、NewGlobalRef()を使用して(ローカル参照から)作成されます。それらは特別なレジストリに追加され、手動で割り当てを解除する必要があります。グローバル参照は、ネイティブコードが複数のJNI呼び出しにわたって参照を保持する必要があるJavaオブジェクトにのみ使用されます。たとえば、Javaに伝播する必要があるネイティブコードトリガーイベントがある場合などです。その場合、JNIコードは、イベントを受信するJavaオブジェクトへの参照を格納する必要があります。

これにより、メモリ管理の問題が少し明らかになることを願っています。

于 2008-10-18T22:11:51.050 に答える
5

ネイティブ リソース (オブジェクト、ファイル記述子など) を再利用するには、いくつかの方法があります。

  1. リソースを解放する finalize() 中に JNI メソッドを呼び出します。finalize を実装しないことを推奨する人もいますが、基本的には、ネイティブ リソースが解放されるかどうかを実際に確認することはできません。メモリなどのリソースの場合、これはおそらく問題にはなりませんが、たとえば予測可能な時間にフラッシュする必要があるファイルがある場合、finalize() はおそらく良い考えではありません。

  2. クリーンアップ メソッドを手動で呼び出します。これは、リソースをクリーンアップする必要があることがわかっている時点がある場合に役立ちます。JNI コードで DLL をアンロードする前に割り当てを解除する必要があるリソースがあるときに、このメソッドを使用しました。後で DLL を再ロードできるようにするには、DLL をアンロードする前に、オブジェクトが実際に割り当て解除されていることを確認する必要がありました。finalize() だけを使用した場合、これは保証されませんでした。これを (1) と組み合わせて、finalize() 中または手動で呼び出されたクリーンアップ メソッドでリソースを割り当てることができます。(クリーンアップ メソッドを呼び出す必要があるオブジェクトを追跡するには、おそらく WeakReferences の正規マップが必要です。)

  3. おそらくPhantomReferenceを使用してこの問題を解決することもできますが、そのような解決策がどのように機能するかは正確にはわかりません。

実際、JNI のドキュメントに関しては、私はあなたと意見を異にする必要があります。JNI 仕様は、ローカル参照とグローバル参照の管理に関するセクションがより詳細に記述されていたとしても、ほとんどの重要な問題について非常に明確であることがわかりました。

于 2008-10-18T16:37:32.333 に答える
0

GCはインスタンスを収集しますが、ネイティブコードで割り当てられた非Javaヒープメモリを自動的に解放しません。c_objectインスタンスを解放するには、クラスに明示的なメソッドが必要です。

これは、ファイナライザーを使用してc_objectが解放されているかどうかを確認して解放し、解放されていない場合はメッセージをログに記録することをお勧めするケースの1つです。

便利なテクニックは、JavaクラスコンストラクターでThrowableインスタンスを作成し、それをフィールドに格納することです(または、フィールドをインラインで初期化するだけです)。ファイナライザーは、クラスが適切に破棄されていないことを検出すると、スタックトレースを出力し、割り当てスタックを特定します。

提案は、ストレートJNIを実行することを避け、gluegenまたはSwigを使用することです(両方ともコードを生成し、静的にリンクできます)。

于 2008-10-18T07:44:58.960 に答える
0

Re:「別の、しかし関連する質問」...「ローカル」コンテキストで使用する場合、jclass、jfieldID、および jmethodID を手動で解放する必要はありません。取得した実際のオブジェクト参照 (jclass、jfieldID、jmethodID ではない) は、DeleteLocalRef で解放する必要があります。

于 2008-10-18T20:10:49.453 に答える