オブジェクトがガベージコレクションされないようにするにはどうすればよいですか?
ファイナライズまたはファントム参照によるアプローチ、またはその他のアプローチはありますか?
私はインタビューでこの質問をされました。インタビュアーは、finalize()
を使用できると提案しました。
参照を保持します。オブジェクトが時期尚早に収集されている場合は、アプリケーションの設計にバグがあることが症状です。
ガベージコレクターは、アプリケーションで参照のないオブジェクトのみを収集します。収集されたオブジェクトを自然に参照するオブジェクトがない場合は、なぜそれを維持する必要があるかを考えてください。
通常は参照を持たないが、オブジェクトを保持したい1つのユースケースは、シングルトンです。この場合、静的変数を使用できます。シングルトンの可能な実装の1つは次のようになります。
public class Singleton {
private static Singleton uniqueInstance;
private Singleton() {
}
public static synchronized Singleton getInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqInstance;
}
}
編集:技術的には、ファイナライザのどこかに参照を保存できます。これにより、参照がなくなったとコレクターが再び判断するまで、オブジェクトが収集されなくなります。ただし、ファイナライザは多くても1回しか呼び出されないため、最初のコレクションの後でオブジェクト(スーパークラスを含む)をファイナライズする必要がないようにする必要があります。ただし、実際のプログラムではこの手法を使用しないことをお勧めします。 (それは私のような同僚がWTFを叫ぶことになります!?;)
protected void finalize() throws Throwable {
MyObjectStore.getInstance().store(this);
super.finalize(); // questionable, but you should ensure calling it somewhere.
}
インタビュアーが探していたトリックの答えは、おそらくメモリリークを強制することによってガベージコレクションがオブジェクトを削除するのを防ぐことができるということを彼があなたに知ってほしいということでしょう。
明らかに、オブジェクトへの参照を長期間有効なコンテキストで保持している場合、それは収集されませんが、それはOPの採用担当者が尋ねたものではありません。これはfinalizeメソッドで発生するものではありません。
Finalizeメソッド内からガベージコレクションを防ぐためにできることは、Thread.yield();
を呼び出す無限ループを作成することです(おそらく、空のループが最適化されないようにするためです)。
@Override
protected void finalize() throws Throwable {
while (true) {
Thread.yield();
}
}
私がここで参照しているのは Elliot Back による記事で、この方法でメモリリークを強制することについて説明しています。
ちょうど別の方法で ファイナライズメソッドは悪です。
最良の方法は nsafe を使用することですが、ByteBuffer
はいくつかのケースで可能な回避策となる場合があります。
「off-heap」メモリというキーワードも検索します。
安全ではない
ByteBuffer
に対する利点:
しかし、働くのは簡単ではありません。この方法は次の記事で説明されています。
これらはすべて次の手順で構成されています。
unsafeにはないsizeof
演算子が必要です。作成方法は次のように尋ねられました: Javaでは、オブジェクトのサイズを決定する最良の方法は何ですか? 。最良のオプションはおそらくinstrument
APIですが、Jarを作成して特別なコマンドラインオプションを使用する必要があります...
sizeof
を取得したら、Unsafe#allocateMemory
で十分なメモリを割り当てます。これは基本的にmalloc
であり、アドレスを返します
通常のヒープオブジェクトを作成し、Unsafe#copyMemory
を使用して割り当てられたメモリにコピーします。これを行うには、ヒープ上のオブジェクトのアドレスとオブジェクトのサイズが必要です
割り当てられたメモリを指すようにObject
を設定してから、Object
をクラスにキャストします。
Unsafeを使用して変数のアドレスを直接設定することはできないようです。そのため、オブジェクトを配列またはラッパーオブジェクトにラップし、Unsafe#arrayBaseOffset
またはUnsafe#objectFieldOffset
を使用する必要があります。
完了したら、割り当てられたメモリをfreeMemory
で解放します
これをsegfaultにしない場合は、例を投稿します:-)
ByteBuffer
安全でない場合の利点:
JLSによると :
ダイレクトバッファの内容は、通常のガベージコレクションされたヒープの外に存在する場合があります。
プリミティブでの使用例:
ByteBuffer bb = ByteBuffer.allocateDirect(8);
bb.putInt(0, 1);
bb.putInt(4, 2);
assert bb.getInt(0) == 1;
assert bb.getInt(4) == 2;
// Bound chekcs are done.
boolean fail = false;
try {
bb.getInt(8);
} catch(IndexOutOfBoundsException e) {
fail = true;
}
assert fail;
関連スレッド:
それでもオブジェクトへの参照がある場合、ガベージコレクションは行われません。それへの参照がない場合は、気にする必要はありません。
つまり、ガベージコレクタはガベージを収集するだけです。任せてください。
finalize
メソッドがファイナライズされるオブジェクトへの参照を隠している場合は、あなたが参照している可能性があると思います。この場合( Java言語仕様 の読み取りが正しい場合)、finalize
メソッドは再実行されませんが、オブジェクトはまだガベージコレクションされません。
これは、偶然の場合を除いて、実際に行うことではありません。
これは、面接のみの時間-あなたがそれを見ることになる質問の1つに聞こえます。 finalize()は、オブジェクトがガベージコレクションされているときに実行されるため、コレクションを防ぐために何かをそこに置くのはかなりおかしいでしょう。通常は、参照を保持するだけで十分です。
ファイナライザーで何かの新しい参照を作成するとどうなるかはわかりません。ガベージコレクターはすでに収集することを決定しているため、最終的にはnull参照になりますか?いずれにしても、悪い考えのようです。例えば.
public class Foo {
static Foo reference;
...
finalize (){
reference = this;
}
}
私はこれがうまくいくか、うまくいくかもしれませんが、GC実装に依存しているか、または「不特定の動作」であるとは思いません。しかし、悪そうに見えます。
重要な点は、オブジェクトを指す実際の参照変数をnullに設定した場合ですが、そのオブジェクトを指すそのクラスのインスタンス変数はnullに設定されていません。オブジェクトは自動的にガベージコレクションの対象になります。オブジェクトをGCに保存する場合は、次のコードを使用してください...
public class GcTest {
public int id;
public String name;
private static GcTest gcTest=null;
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("In finalize method.");
System.out.println("In finalize :ID :"+this.id);
System.out.println("In finalize :ID :"+this.name);
gcTest=this;
}
public static void main(String[] args) {
GcTest myGcTest=new GcTest();
myGcTest.id=1001;
myGcTest.name="Praveen";
myGcTest=null;
// requesting Garbage Collector to execute.
// internally GC uses Mark and Sweep algorithm to clear heap memory.
// gc() is a native method in RunTime class.
System.gc(); // or Runtime.getRuntime().gc();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("\n------- After called GC () ---------\n");
System.out.println("Id :"+gcTest.id);
System.out.println("Name :"+gcTest.name);
}
}
出力:
Finalizeメソッドで。
ファイナライズ:ID:1001
in finalize:ID:Praveen
------- GCが呼び出された後()--------
Id:1001
名前:Praveen
彼らが行っているのは、finalizeを使用してリソースをプールに返し、リソースを保持している実際のオブジェクトがGCではないリソースプール(たとえば、ネットワーク/ db接続、またはスレッド)のパターンなのかどうかed。
愚かな例、Javaのような疑似コードであり、あらゆる種類の同期がない:
class SlowResourceInternal {
private final SlowResourcePool parent;
<some instance data>
returnToPool() {
parent.add(this);
}
}
class SlowResourceHolder {
private final SlowResourceInternal impl;
<delegate actual stuff to the internal object>
finalize() {
if (impl != null) impl.returnToPool();
}
}
これを実現するには、3つの方法があります。1)Heap-Edenスペースサイズを大きくします。 2)静的参照を持つシングルトンクラスを作成します。 3)finalize()メソッドをオーバーライドし、そのオブジェクトを間接参照させないでください。
これにはパターンがあると思います。それが工場のパターンかどうかはわかりません。しかし、すべてのオブジェクトを作成し、それらへの参照を保持する1つのオブジェクトがあります。それらを使い終わったら、ファクトリでそれらを逆参照し、呼び出しを明示的にします。