JVMガベージコレクションでリサイクルできるオブジェクト

1.背景

CやC ++と比較すると、Java言語の最大の特徴は、プログラマーが手動でメモリーを申請して解放する必要がないことです。これはすべてJVMによって行われます。Javaでは、実行時のデータ領域は、プログラムカウンタ、Java仮想マシンスタック、ローカルメソッドスタック、メソッド領域、およびヒープに分割されます。その中で、プログラムカウンタ、仮想マシンスタック、およびローカルメソッドスタックはスレッドプライベートであり、スレッドが破棄されると自動的に解放されます。ガベージコレクションの動作は、ヒープとメソッド領域、主にヒープで発生し、ヒープに格納されるオブジェクトは主にオブジェクトです。それなら当然、いくつかの質問がありますが、どのオブジェクトをリサイクルできますか?リサイクルする方法は?この記事では、主に、JVMによるJavaでのいくつかの参照の最初の質問とリサイクル戦略について説明します。

2.オブジェクトをリサイクルできるかどうかを判断する方法

2.1参照カウント方法

主なアイデアは、オブジェクトに参照カウンターを追加することです。オブジェクトが1回参照されると、カウンターは1増加し、参照されなくなると、カウンターは1減少します。オブジェクトの参照カウンターが0で、誰もそのオブジェクトを使用していないことを示している場合、そのオブジェクトはリサイクルできます。この方法は、実装が比較的簡単で効率的であり、ほとんどの場合に効果的です。ただし、この方法には抜け穴があります。たとえば、A.property = B、B.property = Aの場合、2つのオブジェクトAとBは相互に参照し、他のオブジェクトはAとBを参照しません。参照カウントの考え方によると、AオブジェクトとBオブジェクトの参照カウンターは0ではなく、解放できませんが、実際の状況では、誰もAとBにそれらを使用していないため、メモリリークが発生します。したがって、参照カウント方法は実装が簡単ですが、完全なソリューションではなく、実際のJavaでは使用されません。

2.2到達可能性分析アルゴリズム

主なアイデアは次のとおりです。最初に、リサイクルできない一連のオブジェクト、つまりGCルートを決定します。次に、これらのGCルートから始めて、直接および間接的に参照しているオブジェクトを検索します。最後に、オブジェクトがGCルートによって直接的または間接的に参照されていない場合、そのオブジェクトはリサイクルできます。この方法は、循環参照の問題を効果的に解決できます。実際には、Javaもこの判断方法を使用します。では、問題は、どのオブジェクトをGCルートとして使用できるかということです。ここでは、MATツールを使用して観察できます。次のデモを実行します。

import java.util.concurrent.TimeUnit;

public class GCRootsTest {
    public static void main(String[] args) throws InterruptedException {
        Object o = new Object();
        TimeUnit.SECONDS.sleep(100);
    }
}

ターミナルウィンドウでメインスレッドがスリープ状態のときに、jmap -dump:format = b、live 、file = heapdump.bin 2872コマンド rawヒープダンプスナップショットダンプファイルを実行します。ここで、2872はプロセスIDであり、jpsコマンドを使用できます。次に、MATツールを使用してダンプファイルを開くと、GCルートとして使用できるオブジェクトが4種類あることがはっきりとわかります。これについては、以下で詳しく説明します。

最初のカテゴリ、システムクラスオブジェクト(システムクラス)。たとえば、java.lang.StringのClassオブジェクトはよく理解されています。これらのコアシステムクラスオブジェクトがリサイクルされると、プログラムは実行できなくなります。

2番目のカテゴリ、ネイティブメソッドによって参照されるオブジェクト。

3番目のカテゴリは、アクティブなスレッドで参照されているオブジェクトです。コード内の変数oが指すObjectオブジェクトは、GCルートと見なすことができることがわかります。

4番目のカテゴリは、ロックされているオブジェクトです。

3.Javaでのいくつかの参照

到達可能性分析アルゴリズムでは、オブジェクトをリサイクルできるかどうかを判断するために、主にオブジェクトへの参照がGCルートから見つかるかどうかに依存します。Javaには、強い参照、ソフト参照、弱い参照、ファントム参照の4種類の参照があります。このようにして、さまざまな参照によって示されるオブジェクトに対してさまざまなリサイクル戦略を採用できます。たとえば、強い参照がオブジェクトを指している場合、OOMが発生しても、このオブジェクトは確実にリサイクルされません。弱参照が指すオブジェクトについては、ガベージコレクションが発生している限り、オブジェクトはリサイクルされます。以下は、さまざまな参照の使用法の詳細な紹介です。

3.1強力な参照

いわゆる強い参照は、Object obj = new Object()の参照と同様に、最も頻繁に使用されるものです。ガベージコレクターは、強力な参照によってポイントされたオブジェクトを再利用することはありません。

3.2ソフトリファレンス

ソフト参照。JavaのSoftReferenceクラスを使用してソフト参照を実装します。次のコードでは、softReferenceはObjectオブジェクトをソフト参照として参照し、otherObject変数はソフト参照のgetメソッドを介してObjectオブジェクトを間接的に参照できます。

    public static void main(String[] args) {
        // 软引用
        SoftReference<Object> softReference = new SoftReference<>(new Object());
        Object otherObject = softReference.get();
    }

ソフト参照で指定されたオブジェクトについては、メモリが不足している場合、オブジェクトはリサイクルされます。この現象を実証するには、JVMのヒープメモリを10M(-Xms10M -Xmx10M)に設定します。次のコードの主なロジックは次のとおりです。5つのSoftReferenceオブジェクトをリストコレクションに追加します。各SoftReferenceオブジェクトは2Mのサイズのバイト配列を指し、追加が完了した後、リストを反復処理し、が指すポイントを出力します。リストオブジェクトの各ソフト参照。

public class ReferenceTest {

    private static final int _2M = 2 * 1024 * 1024;

    public static void main(String[] args) {
        List<SoftReference<Object>> list = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            SoftReference<Object> softReference = new SoftReference<>(new byte[_2M]);
            list.add(softReference);
        }

        System.out.println("List集合中的软引用:");
        for (int i = 0; i < 5; i++) {
            System.out.println(list.get(i));
        }

        System.out.println("--------------------------");
        System.out.println("List集合中的软引用指向的对象:");
        for (int i = 0; i < 5; i++) {
            System.out.println(list.get(i).get());
        }
    }
}

ヒープメモリが10Mのときに実行された上記のコードの結果を次の図に示します。最初の3つのソフト参照が指すオブジェクトがガベージコレクターによってリサイクルされていることがわかります。これは、ヒープメモリが十分でなく、ソフト参照が指すオブジェクトがリサイクルされているためです。

通常の状況では、ソフト参照が指すオブジェクトはリサイクルされます。その後、ソフト参照は意味を持たないため、ガベージコレクターでリサイクルする必要があります。この効果を実現するために、通常、ソフト参照は参照キューで使用されます。使用法は次のコードのとおりです。ソフト参照を参照キューに関連付けると、ソフト参照が指すオブジェクトがリサイクルされるときに、ソフト参照が自動的に参照キューに追加されます。このとき、特定の戦略を使用して、これらのソフト参照オブジェクトをリサイクルできます。

public class ReferenceTest {

    private static final int _2M = 2 * 1024 * 1024;

    public static void main(String[] args) {
        List<SoftReference<Object>> list = new ArrayList<>();
        // 引用队列
        ReferenceQueue<Object> queue = new ReferenceQueue<>();
        for (int i = 0; i < 5; i++) {
            // 同时将软引用关联引用队列,当软引用指向的对象被回收时,该软引用会加入到队列
            SoftReference<Object> softReference = new SoftReference<>(new byte[_2M], queue);
            list.add(softReference);
        }

        // 移除List中,指向对象已经被回收的软引用
        Reference<?> poll = queue.poll();
        while (null != poll) {
            list.remove(poll);
            poll = queue.poll();
        }

        System.out.println("List集合中的软引用:");
        for (SoftReference<Object> reference : list) {
            System.out.println(reference);
        }

        System.out.println("-------------------------------------");
        System.out.println("List集合中的软引用指向的对象:");
        for (SoftReference<Object> reference : list) {
            System.out.println(reference.get());
        }
    }
}

実行結果は以下のとおりです。

3.3弱い参照

弱い引用は、柔らかい引用と比較して、引用が弱いです。ガベージコレクションが発生するたびに、弱参照が指すオブジェクトが収集されます。言うことはあまりありませんが、コードに移動するだけです。ソフト参照デモと同様に、唯一の違いは、各バイト配列のサイズが2Kになっているため、ヒープが確実に格納され、ガベージコレクションが発生しないことです。

public class WeakReferenceTest {
    private static final int _2K = 2 * 1024;

    public static void main(String[] args) {
        List<WeakReference<byte[]>> list = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            WeakReference<byte[]> reference = new WeakReference<>(new byte[_2K]);
            list.add(reference);
        }

        System.out.println("List集合中的软引用:");
        for (WeakReference<byte[]> reference : list) {
            System.out.println(reference);
        }

        System.out.println("-------------------------------------");
        System.out.println("List集合中的软引用指向的对象:");
        for (WeakReference<byte[]> reference: list) {
            System.out.println(reference.get());
        }
    }
}

実行します。弱参照が指すオブジェクトがリサイクルされていないことがわかります。

上記のコードに基づいて、ガベージコレクションが人為的に実行されます。コードは次のとおりです。

public class WeakReferenceTest {
    private static final int _2K = 2 * 1024;

    public static void main(String[] args) {
        List<WeakReference<byte[]>> list = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            WeakReference<byte[]> reference = new WeakReference<>(new byte[_2K]);
            list.add(reference);
        }

        System.gc(); // 手动垃圾回收
        System.out.println("List集合中的弱引用:");
        for (WeakReference<byte[]> reference : list) {
            System.out.println(reference);
        }

        System.out.println("-------------------------------------");
        System.out.println("List集合中的弱引用指向的对象:");
        for (WeakReference<byte[]> reference: list) {
            System.out.println(reference.get());
        }
    }
}

実行します。弱参照によって示されるオブジェクトがリサイクルされていることがわかります。ソフト参照と同様に、弱参照も参照キューと組み合わせて使用​​できるため、ここでは繰り返しません。

3.4ファントム参照

ソフト参照や仮想参照とは異なり、仮想参照は参照キューで使用する必要があり、仮想参照が指すオブジェクトは仮想参照から取得できません。Javaでは、仮想参照はPhantomReferenceクラスで表されます。PhantomReferenceのソースコードから、仮想参照を呼び出すgetメソッドは常にnullを返し、PhantomReferenceは参照キューを含むパラメーター化されたコンストラクターのみを提供することがわかります。つまり、仮想参照は参照キューと組み合わせて使用​​する必要があります。

public class PhantomReference<T> extends Reference<T> {

    public T get() {
        return null;
    }

    public PhantomReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
    }

}

それが指すオブジェクトはファントム参照では取得できないので、ファントム参照の用途は何ですか?実際、仮想参照をオブジェクトに関連付ける唯一の目的は、オブジェクトがガベージコレクションされたときにシステム通知を受信することです。ガベージコレクターがオブジェクトをリサイクルしようとしているときに、それに関連付けられたファントム参照があることがわかった場合、ガベージコレクションの後にファントム参照を参照キューに追加します。関連付けられたファントム参照がデキューされる前に、追加されません。完全に破壊されます。オブジェクト。上記の説明はまだ理解しにくいです。実際、仮想参照の典型的な使用シナリオは、DirectByteBufferクラスと組み合わせてそれらを使用することです。DirectByteBufferクラスはオフヒープメモリ(JVMが占有する部分を除くサーバーメモリ内)を使用します。これにより、データをカーネルにコピーする必要がなくなるため、効率はByteBufferよりもはるかに高くなります(ここでの焦点は仮想参照、DirectByteBufferを理解したいクラスの基本原理、インターネット上でリソースを見つけることができます)、そのメモリ図は次のとおりです。

DirectByteBufferクラスは非常に効率的ですが、オフヒープメモリのJVMのガベージコレクタはリサイクルできないため、DirectByteBufferクラスで使用されるオフヒープメモリは慎重に処理する必要があります。そうしないと、サーバーメモリリークが発生しやすくなります。 。この問題を解決するには、ファントム参照が役立ちます。DirectByteBufferクラスの作成とリサイクルは、主に次のステップに分かれています。

  • DirecByteBufferオブジェクトを作成すると、それ自体を指すCleaner仮想参照オブジェクトが同時に作成され、Deallocatorオブジェクトが同時にCleanerに渡されます。

  •  Cleanerクラスの親クラスはPhantomReferenceであり、祖父クラスはReferenceです。Referenceクラスは、初期化されるとReferenceHandlerスレッドを開始します

  • DirectByteBufferオブジェクトがリサイクルされると、Cleanerオブジェクトが参照キューに追加されます
  • このとき、ReferenceHandlerスレッドはCleanerオブジェクトのcleanメソッドを呼び出して、オフヒープメモリのリカバリを完了します。

  • cleanメソッドはDeallocatorのrunメソッドを呼び出し、最後にUnsafeクラスを介してオフヒープメモリのリカバリを完了します。

要約すると、これは1つの文であり、DirectByteBufferオブジェクトを仮想参照に関連付けます。DirectByteBufferがリサイクルされると、仮想参照オブジェクトが参照キューに追加され、仮想参照オブジェクトがオフヒープメモリを解放します。(興味のある方やパートナーは、以下のDirectByteBufferのソースコードに従うことができます)

4.まとめ

  • JVMは、到達可能性分析アルゴリズムを使用して、ヒープ内のどのオブジェクトをリサイクルできるかを判別します。
  • GCルートとして使用できるオブジェクトには、システムオブジェクト、ネイティブメソッドによって参照されるオブジェクト、アクティブスレッドによって参照されるオブジェクト、およびロックされているオブジェクトの4つの主要なタイプがあります。
  • Javaで一般的に使用される参照には、強参照、ソフト参照、弱参照、ファントム参照の4つの主要なタイプがあります。JVMには、さまざまな参照が指すオブジェクトに対してさまざまなリサイクル戦略があります。
  • 強い参照によってポイントされたオブジェクトの場合、OOMが発生しても、ガベージコレクターはそれらをリサイクルしません。
  • ソフト参照が指すオブジェクトの場合、メモリが不足すると、ガベージコレクタはそれを再利用します。この機能を使用してキャッシュを実装できます。メモリが不足すると、JVMはこれらのキャッシュを自動的にクリーンアップします。
  • 弱参照が指すオブジェクトの場合、ガベージコレクションが発生すると、ガベージコレクターはそれらをリサイクルします。
  • 仮想参照の場合、それらは参照キューと組み合わせて使用​​する必要があり、仮想参照が指すオブジェクトは仮想参照から取得できません。仮想参照をオブジェクトに関連付ける唯一の目的は、次の場合にシステム通知を受信することです。オブジェクトはガベージコレクションです。

おすすめ

転載: blog.csdn.net/weixin_40988088/article/details/114769014