web-dev-qa-db-ja.com

JavaFXプロパティリスナーとバインディングをクリーンアップします(メモリリーク)

これらの2つの質問に対する簡単な答えは見つかりませんでした。

  1. プロパティインスタンスを削除する前にリスナーを削除する必要がありますか(リスナーは他の場所では使用されません)?

    BooleanProperty bool = new SimpleBooleanProperty();
    bool.addListener(myListener);
    bool.removeListener(myListener); // is it necessary to do this?
    bool = null;
    
  2. プロパティインスタンスを削除する前に、単方向の境界プロパティのバインドを解除する必要がありますか?

    BooleanProperty bool = new SimpleBooleanProperty();
    bool.bind(otherBool);
    bool.unbind(); // is it necessary to do this?
    bool = null;
    
21
dpelisek

ケース1

myListenerが「他の場所では使用されていない」ため、[method-]ローカル変数であるとすると、答えはnoです。 。ただし、一般的なケースでは、答えはほとんどnoですが、yesになることもあります。

myListenerが強力に到達可能である限り、ファイナライズの対象になることはなく、メモリを消費し続けます。たとえば、これはmyListenerが「通常」宣言されたstatic変数である場合に当てはまります(* Javaのすべての「通常」参照はstrong参照*)。ただし、myListenerがローカル変数の場合、現在のメソッド呼び出しとbool.removeListener(myListener)が戻った後、オブジェクトに到達できなくなります。オブザーバーとObservableの両方が範囲外になり、最終的には最終決定されます。私自身からの引用 ブログ投稿 この回答についてはもっとうまくいくかもしれません画像:

箱を海に投げても、箱の中にいる猫のことを知っているかどうかは関係ありません。ボックスに到達できない場合、猫も到達できません。

理論

ここでの状況を完全に理解するには、Javaオブジェクト( ソース )のライフサイクルを思い出す必要があります。

オブジェクトは、参照オブジェクトをトラバースせずに何らかのスレッドで到達できる場合、強力に到達可能です。新しく作成されたオブジェクトは、それを作成したスレッドから強力に到達可能です。 [..]オブジェクトは、強く[..]到達可能ではないが、弱い参照をトラバースすることで到達できる場合、弱く到達可能です。弱到達可能オブジェクトへの弱参照がクリアされると、オブジェクトはファイナライズの対象になります。

静的変数の場合、クラスがロードされている限り、これらは常にアクセス可能であり、したがって到達可能です。静的参照がガベージコレクターの仕事を妨げるものになりたくない場合は、代わりに WeakReference を使用するように変数を宣言できます。 JavaDocによると:

弱参照オブジェクト[..]は、それらの指示対象がファイナライズ可能になり、ファイナライズされてから再利用されることを妨げません。 [..]ガベージコレクターが、ある時点でオブジェクトが弱く到達可能であると判断したとします。その時点で、そのオブジェクトへのすべての弱参照をアトミックにクリアします[..]。同時に、以前は到達可能性が低かったすべてのオブジェクトをファイナライズ可能として宣言します。

明示的な管理

説明のために、JavaFXスペースシミュレーションゲームを作成するとします。 Observable惑星が宇宙船オブザーバーの視界に移動するたびに、ゲームエンジンは宇宙船を惑星に登録します。惑星が見えなくなるときはいつでも、ゲームエンジンは Observable.removeListener() を使用して、惑星のオブザーバーとしての宇宙船も削除する必要があることは明らかです。そうでなければ、宇宙船が宇宙を飛行し続けると、メモリがリークします。最終的に、ゲームは50億個の観測された惑星を処理できなくなり、 OutOfMemoryError でクラッシュします。

大多数のJavaFXリスナーとイベントハンドラーのライフサイクルはObservableのライフサイクルと並行しているため、アプリケーション開発者は何も心配する必要がないことに注意してください。たとえば、 TextField を作成し、ユーザー入力を検証するリスナーをテキストフィールドのtextPropertyに登録します。テキストフィールドがくっついている限り、リスナーにくっついてもらいたいのです。遅かれ早かれ、テキストフィールドは使用されなくなり、ガベージコレクションが行われると、検証リスナーもガベージコレクションされます。

自動管理

スペースシミュレーションの例を続けるために、私たちのゲームではマルチプレイヤーのサポートが制限されており、すべてのプレイヤーがお互いを観察する必要があると仮定します。おそらく、各プレイヤーはキルメトリックのローカルスコアボードを保持しているか、ブロードキャストされたチャットメッセージを監視する必要があります。理由はここでは重要なポイントではありません。プレイヤーがゲームを終了するとどうなりますか?明らかに、リスナーが明示的に管理されていない場合(削除)、終了したプレーヤーはファイナライズの対象になりません。他のプレイヤーはオフラインプレイヤーへの強い参照を維持します。リスナーを明示的に削除することは依然として有効なオプションであり、おそらく私たちのゲームにとって最も好ましい選択ですが、少し邪魔になり、より洗練された解決策を見つけたいとしましょう。

ゲームエンジンは、オンラインである限り、すべてのプレーヤーをオンラインで強く参照し続けることを私たちは知っています。したがって、ゲームエンジンが強力な参照を保持している限り、宇宙船が互いの変更やイベントをリッスンするようにします。 「理論」のセクションを読むと、確かにWeakReferenceは解決策のように聞こえます。

ただし、WeakReferenceで何かをラップするだけでは、ソリューション全体ではありません。それはめったにありません。 「指示対象」への最後の強い参照がnullに設定されているか、そうでなければ到達不能になった場合、その指示対象はガベージコレクションの対象になります(SoftReference)を使用して到達しました。しかし、WeakReferenceはまだぶらぶらしています。アプリケーション開発者は、WeakReference自体が配置されたデータ構造から削除されるように、配管を追加する必要があります。そうでない場合は、メモリリークの重大度を軽減した可能性がありますが、動的に追加された弱いため、メモリリークは引き続き存在します。参照もメモリを消費します。

私たちにとって幸運なことに、JavaFXは「自動削除」のメカニズムとしてインターフェース WeakListener とクラス WeakEventHandler を追加しました。関連するすべてのクラスのコンストラクターは、クライアントコードによって提供される実際のリスナー/ハンドラーを受け入れますが、弱参照を使用してリスナー/ハンドラーを格納します。

WeakEventHandlerのJavaDocを見ると、クラスがEventHandlerを実装していることがわかります。したがって、WeakEventHandlerは、EventHandlerが予期される場所であればどこでも使用できます。同様に、WeakListenerの既知の実装はInvalidationListenerまたはChangeListenerが予想される場所ならどこでも使用できます。

WeakEventHandlerのソースコードを調べると、クラスは基本的にラッパーにすぎないことがわかります。彼の指示対象(実際のイベントハンドラー)がガベージコレクションされると、WeakEventHandlerは何もしないことで「動作を停止」します WeakEventHandler.handle() が呼び出されます。 WeakEventHandlerは、彼がどのオブジェクトに接続されているかを知りません。また、接続されたとしても、イベントハンドラーの削除は均一ではありません。ただし、WeakListenerのすべての既知の実装クラスには競争上の優位性があります。コールバックが呼び出されると、それらが登録されているObservableへの参照が暗黙的または明示的に提供されます。したがって、WeakListenerの指示対象がガベージコレクションされると、最終的にWeakListener実装は、WeakListener自体がObservableから削除されることを確認します。

まだ明確でない場合、私たちの宇宙シミュレーションゲームの解決策は、ゲームエンジンにすべてのオンライン宇宙船への強力な参照を使用させることです。宇宙船がオンラインになると、他のすべてのオンライン宇宙船は、 WeakInvalidationListener などの弱いリスナーを使用して新しいプレーヤーに登録されます。プレーヤーがオフラインになると、ゲームエンジンはプレーヤーへの強い参照を削除し、プレーヤーはガベージコレクションの対象になります。ゲームエンジンは、他のプレーヤーのリスナーとしてオフラインプレーヤーを明示的に削除することを気にする必要はありません。

ケース2

いいえ。次に何を言うかをよりよく理解するために、最初に私のケース1の回答を読んでください。

BooleanPropertyBaseotherBoolへの強力な参照を格納します。これ自体がotherBoolに常に到達可能になるわけではないため、メモリリークが発生する可能性があります。 boolが到達不能になると、格納されているすべての参照も到達不能になります(他の場所に格納されていない場合)。

BooleanPropertyBaseは、バインドするプロパティのObserverとして自分自身を追加することによっても機能します。ただし、これは、私のケース1の回答で説明したWeakListenersとほぼ同じように機能するクラスに自分自身をラップすることによって行われます。したがって、boolを無効にすると、otherBoolから削除されるのは時間の問題になります。

30

私はケース1の答えに完全に同意しますが、ケース2はもう少しトリッキーです。 The bool.unbind()呼び出しが必要です。省略した場合、小さなメモリリークを引き起こします。

次のループを実行すると、アプリケーションは最終的にメモリ不足になります。

BooleanProperty p1 = new SimpleBooleanProperty();
while(true) {
    BooleanProperty p2 = new SimpleBooleanProperty();
    p2.bind(p1)
}

BooleanPropertyBaseは、通常、実際のWeakListener( WeakListener インターフェイスの実装)を使用せず、中途半端なソリューションを使用しています。すべての「p2」インスタンスは最終的にガベージコレクションされますが、空のWeakReferenceを保持しているリスナーは、「p2」ごとに永久にメモリに残ります。 BooleanPropertyBaseだけでなく、すべてのプロパティにも同じことが当てはまります。 ここで説明されています 詳細に、Java 9で修正されていると言われています。

ほとんどの場合、このメモリリークは、バインド解除されていないすべてのバインディングに対して数十バイトしか残らないため、気付かないでしょう。しかし、場合によっては、それは私に本当の問題を引き起こしました。良い例は、頻繁に更新されるテーブルのテーブルセルです。その後、セルは常に異なるプロパティに再バインドされ、メモリ内のこれらの残りはすぐに蓄積されます。

7
Jan X Marek