[Effective Java](06)期限切れのオブジェクト参照を削除

3093 ワード

Java言語にはゴミ収集(GC)回収プログラムがあり、オブジェクトを作成した後、プログラマが手動でオブジェクトを回収する必要はありませんが、場合によってはメモリが漏れることもあります.
ごみ回収をサポートする言語では,メモリ漏洩は隠蔽されており,このようなメモリ漏洩は「無意識のオブジェクト保持」と呼ぶのが適切である.1つのオブジェクト参照が無意識に保持されると、回収メカニズムはこのオブジェクトを処理するだけでなく、このオブジェクトによって参照された他のすべてのオブジェクトを処理することもなく、少量のオブジェクト参照だけが無意識に保持されても、多くのオブジェクトがゴミ回収メカニズムから除外され、これにより、パフォーマンスに潜在的な大きな影響を及ぼします.
メモリの漏洩には、主に次の3つの方法があります.
1.期限切れのオブジェクト参照によるメモリリーク
サンプルコードは次のとおりです.
public class Stack {
    private Object[] elements;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;
    
    public Stack() {
        elements = new Object[DEFAULT_INITIAL_CAPACITY];
    }
    
    public void push(Object obj) {
        ensureCapacity();
        elements[size++] = e;
    }
    
    public Object pop() {
        if(size == 0) {
            throw new EmptyStackException();
        }
        return elements[--size];
    }
    
    /**
     * Ensure space for at least one more element, roughly
     * doubling tha capacity each time the array needs to grow.
     */
    private void ensureCapacity() {
        if(elements.length == size) {
            elements = Arrays.copyOf(elements,  2 * size + 1);
        }
    }
}

解析:スタックが先に成長し、収縮している場合、スタックから飛び出したオブジェクトはゴミ回収として扱われません.スタックを使用するプログラムがこれらのオブジェクトを参照していなくても、スタック内部でこれらのオブジェクトの期限切れの参照(obsolete reference)が維持されているため、スタックは回収されません.
期限切れ参照:これ以上解除されない参照です.上記のコードでは、elements配列の「アクティブ部分(active portion)」以外の参照は期限切れです.アクティブな部分とは、elementsの下付き文字がsizeより小さい要素を指します.
解決策:オブジェクトの参照が期限切れになると、これらの参照が空になります.このような利点の1つは、それらが後で誤った参照を受けると、プログラムは直ちにNullPointerExceptionの異常を放出する(プログラム内のエラーをできるだけ早く検出することは常に有益である).
public Object pop() {
    if(size == 0) {
        throw new EmptyStackException();
    }
    Object res = elements[--size];
    elements[size] = null;    //Eliminate obsolete reference
    return res;
}

注意:空のオブジェクト参照は、仕様の動作ではなく例外である必要があります.
  • 期限切れの参照を除去する最善の方法は、その参照を含む変数をライフサイクルを終了させることであり、すなわち、変数に最もコンパクトな役割ドメインを定義することである.
  • クラスが自分でメモリを管理している限り、プログラマーはメモリ漏洩の問題を警戒しなければならない.

  • 2.キャッシュ内のオブジェクトによるメモリリーク
    メモリ漏洩のもう一つの一般的なソースはキャッシュです.オブジェクトアプリケーションはキャッシュに保存されています.オブジェクトが使用されなくなった場合、クリーンアップされずに忘れられやすくなります.したがって、オブジェクトリファレンスはキャッシュに保存されますが、論理的には使用されていませんが、そのオブジェクトはGCで回収されません.リファレンスがまだ指定されているためです.
    解決策:キャッシュ内のリファレンスオブジェクトがいつ役に立たなくなったかを知っておくと、意味があります.このとき、キャッシュ内のオブジェクトリファレンスをクリーンアップできます.
  • 必要なキャッシュ・アイテムのライフサイクルが値ではなくキーの外部参照によって決定される場合、WeakHashMapはキャッシュを表すことができる.(コード例、続き・・)
  • のより一般的な状況は、「キャッシュ・アイテムのライフサイクルに意味があるかどうか」は容易には判断できません.時間が経つにつれて、そのアイテムはますます価値がなくなります.この場合、キャッシュは時々無駄なアイテムを明らかにする必要があります.バックグラウンド・プロセス(TimerまたはScheduledThreadPoolExecutor)を使用して完了することができます.キャッシュに新しいエントリを追加するついでにクリーンアップを行うこともできます(LinkedHashMapクラスのremoveEldestEntryメソッドはこのスキームを実現しやすい).より複雑なキャッシュではjavaを直接使用する必要があります.lang.ref. (コード例、続き・・)
  • 3.Listenerおよびその他のコールバックによるメモリリーク
    メモリ漏洩の3つ目の一般的なソースはリスナーと他のコールバックです.私たちはAPIを実現しました.ユーザーはこのAPIにコールバックを登録しましたが、カーブ登録を表示していません.いくつかの方法を取らない限り、彼らは蓄積し、コールバックがすぐにゴミ回収として扱われることを確保する最善の方法は、弱い参照(weak reference)を保存することです.たとえば、WeakHashMapのキーにのみ保存します.(コード例、続き・・・)