GCとメモリ漏洩

5610 ワード

1.GCメカニズムがある以上、なぜメモリが漏れることがあるのでしょうか.
    理論的にはJavaはゴミ回収メカニズム(GC)があるためメモリ漏洩の問題は存在しない(これもJavaがサーバ側プログラミングに広く使われている重要な原因である).しかし、実際の開発では、GCで回収できない無駄だが到達可能なオブジェクトが存在する可能性があり、メモリ漏洩の発生にもつながる.
    たとえばhibernateのSession(一級キャッシュ)のオブジェクトは永続的であり、ゴミ回収器はこれらのオブジェクトを回収しないが、これらのオブジェクトには不要なゴミオブジェクトが存在する可能性があり、適時にクローズ(close)またはフラッシュ(flush)の一級キャッシュをクリアしないとメモリ漏洩を引き起こす可能性がある.
次の例のコードでもメモリが漏洩します.
import java.util.Arrays;
import java.util.EmptyStackException;

public class MyStack {
	private T[] elements;
	private int size = 0;
	private static final int INIT_CAPACITY = 16;

	public MyStack() {
		elements = (T[]) new Object[INIT_CAPACITY];
	}

	public void push(T elem) {
		ensureCapacity();
		elements[size++] = elem;
	}

	public T pop() {
		if (size == 0)
			throw new EmptyStackException();
		return elements[--size];
	}

	private void ensureCapacity() {
		if (elements.length == size) {
			elements = Arrays.copyOf(elements, 2 * size + 1);
		}
	}
}

上のコードはスタック(先進後出(FILO))構造を実現しており、一見明らかな問題はないようで、あなたが書いた様々なユニットテストを通じても問題ありません.しかし、その中のpopメソッドにはメモリ漏洩の問題があり、popメソッドでスタック内のオブジェクトをポップアップすると、スタックを使用するプログラムがこれらのオブジェクトを参照しなくても、スタック内部でこれらのオブジェクトに対する期限切れの参照(obsolete reference)が維持されているため、オブジェクトはゴミ回収として扱われません.ごみ回収をサポートする言語では,メモリ漏洩は隠蔽されており,このメモリ漏洩は実は無意識の対象保持である.オブジェクトリファレンスが無意識に保持されている場合、ゴミ回収機はそのオブジェクトを処理せず、そのオブジェクトリファレンスの他のオブジェクトも処理しません.このようなオブジェクトが少数であっても、多くのオブジェクトがゴミ回収から除外され、パフォーマンスに大きな影響を与え、極端な場合にはDisk Pagingが発生する可能性があります.(物理メモリはハードディスクの仮想メモリとデータを交換する)、OutOfMemoryErrorをもたらすこともあります.
2.JavaではなぜGCメカニズムがあるのでしょうか?
    安全性の考慮
    メモリの漏洩を減らす--erase memory leak in some degree.
    プログラマーの仕事量を減らす.--Programmers don't worry about memory releasing.
3.JavaのGCにはどのようなメモリを回収する必要がありますか?
    メモリ実行時のJVMには、メモリを管理するための実行時データ領域がある.主に、プログラムカウンタ(Program Counter Register)、仮想マシンスタック(VM Stack)、ローカルメソッドスタック(Native Method Stack)、メソッド領域(Method Area)、スタック(Heap)の5つの部分がある.
    一方、プログラムカウンタ、仮想マシンスタック、ローカルメソッドスタックは、スレッドごとにプライベートなメモリ空間であり、スレッドごとに発生し、スレッドに従って消滅する.たとえば、スタック内のスタックフレームごとにどれだけのメモリが割り当てられるかは、クラス構造がどのメモリであるかを決定する際に基本的に知られているため、この3つの領域のメモリ割り当てと回収は、メモリ回収の問題を考慮する必要がないため、決定される.
    しかし、メソッド領域とスタックは異なり、1つのインタフェースの複数の実装クラスに必要なメモリが異なる可能性があります.プログラムの実行中にどのオブジェクトが作成されるかを知ることができます.このメモリの割り当てと回収は動的で、GCは主にこのメモリに注目しています.
    要するに、GCが主に回収するメモリはJVMのメソッド領域とスタックである.
4.JavaのGCはいつごみを回収しますか?
面接では、ある相手が死んだとどう判断するか(実際に筆者も出会ったことがある):
考えやすい答えの一つは、1つのオブジェクトに参照カウンタを追加することです.どこかで参照するたびに、カウンタ値に1を追加します.参照が失効すると、カウンタ値が1.減少します.カウンタの値が0の場合、このオブジェクトは使用されなくなり、死んだと判断します.簡単で直感的ではありません.しかし、残念です.このやり方は間違っています.なぜ間違っていますか.事実ですで、参照カウント法を使用すると、ほとんどの場合、良い解決策ですが、実際のアプリケーションでは多くのケースがありますが、オブジェクト間のループ参照の問題を解決することはできません.たとえば、オブジェクトAのフィールドがオブジェクトBを指し、オブジェクトBのフィールドがオブジェクトAを指していますが、実際には2人とも使用されませんが、カウンタの値は永遠に使用されません.いずれも0ではなく、回収されず、メモリ漏洩が発生します.
そこで、正しいアプローチはどうでしょうか.Java、C#などの言語では、比較的主流のオブジェクトが死んだと判定する方法として、達成性分析(Reachability Analysis)があります.生成されたすべてのオブジェクトは「GC Roots」と呼ばれるルートのサブツリーです.GC Rootsから検索し、検索したパスをリファレンスチェーン(Reference Chain)と呼びますオブジェクトがGC Rootsに到達する参照チェーンがない場合、このオブジェクトは到達できない(参照できない)、すなわちGCによって回収されると称される.
リファレンスカウンタでも達成性分析でも、オブジェクトが生存しているかどうかを判定するのはリファレンスに関係します!では、オブジェクトのリファレンスをどのように定義しますか?
メモリ容量が十分な場合はメモリに保存できます.ゴミ回収後もメモリ容量が非常に緊張している場合は、これらのオブジェクトを捨てることができます.そのため、必要に応じて、次の4つの参照が与えられます.引用タイプによっては、GC回収時にも異なる操作があります.
    1)強引用(Strong Reference):Object obj=new Object()です.強引用が存在する限り、GCは参照されたオブジェクトを回収しません.
    2)ソフトリファレンス(Soft Reference):まだ役に立つが必要でないオブジェクトについて説明します.システムにメモリオーバーフローが発生する前に、これらのオブジェクトを回収範囲に入れて二次回収します(つまり、システムにメモリオーバーフローが発生してから回収します).
    3)弱引用(Weak Reference):ソフト引用よりも程度が弱い.これらのオブジェクトは次のGCまでしか生存できない.GCが動作すると、メモリが十分かどうかにかかわらず回収される(すなわちGCを行う限り回収される).
    4)ダミーリファレンス(Phantom Reference):オブジェクトにダミーリファレンスが存在するかどうかは、生存時間に影響を与えません.
メソッド領域で回収する必要があるのは、廃棄された定数と不要なクラスです.
    1.廃棄された定数の回収.ここでは参照カウントを見ればよい.この定数を参照する対象がなければ安心して回収できる.
    2.無用の類の回収.無用の類とは何か.
        A.クラスのすべてのインスタンスが回収されました.つまり、Javaスタックにはクラスのインスタンスが存在しません.
        B.クラスをロードしたClassLoaderはすでに回収されている.
        C.クラスに対応するjava.lang.Classオブジェクトはどこからも参照されず、どこからでも反射によってクラスにアクセスする方法がありません.
要するに、
スタック内のオブジェクトについては、主に達成性分析で1つのオブジェクトにリファレンスが存在するか否かを判断し、そのオブジェクトにリファレンスがなければ回収されるべきである.また、実際のリファレンスに対する我々のニーズに応じて、4つのリファレンスに分けられ、各リファレンスの回収メカニズムも異なる.
メソッド領域の定数とクラスでは、1つの定数にオブジェクトが参照されていない場合に回収されます.クラスでは、不要なクラスと判定できれば回収されます.
4.开発中にメモリオーバーフローに遭遇したことがありますか?原因はどれらがありますか?解决方法はどれらがありますか?
メモリオーバーフローの原因はいくつかありますが、一般的には次のようなものがあります.
1.メモリにロードされたデータ量が大きすぎて、一度にデータベースからデータを取り出しすぎた場合.
2.集合クラスにはオブジェクトへの参照があり、使用後は空になっていないため、JVMが回収できない.
3.コードにデッドループまたはループが存在し、重複しすぎるオブジェクトエンティティが存在する.
4.使用するサードパーティ製ソフトウェアのBUG;
5.起動パラメータのメモリ値の設定が小さすぎる;
メモリオーバーフローの解決策:
      まず、JVM起動パラメータを変更し、メモリを直接追加します.(-Xms、-Xmxパラメータは必ず忘れないでください.)
ステップ2では、エラー・ログを確認し、OutOfMemoryエラーの前に他の例外またはエラーがあるかどうかを確認します.
第3歩では、コードを調べて分析し、メモリオーバーフローが発生する可能性のある場所を特定します.
重点的に以下の点を調べます.
1.データベース・クエリーで、すべてのデータを取得したクエリーが一度にあるかどうかをチェックします.一般的に、一度に10万個をメモリに記録すると、メモリ・オーバーフローが発生する可能性があります.この問題は隠れていて、オンラインになる前に、データベースのデータが少なく、問題が発生しにくくなります.オンラインになった後、データベースのデータが多くなり、一度のクエリーでメモリ・オーバーフローが発生する可能性があります.これは、データベース・クエリーに対してできるだけページ単位でクエリーします.
2.コードにデッドループまたは再帰呼び出しがあるかどうかを確認します.
3.新しいオブジェクトエンティティを生成する大きなループが繰り返されているかどうかを確認します.
4.データベース・クエリーで、すべてのデータを取得したクエリーが一度にあるかどうかを確認します.一般的に、一度に10万個のレコードをメモリに記録すると、メモリがオーバーフローする可能性があります.この問題は、オンラインになる前に、データベースに隠されています.  データが少なく、問題が発生しにくいため、オンラインになると、データベースにデータが多くなり、1回のクエリでメモリオーバーフローを引き起こす可能性があります.そのため、データベースクエリに対してはできるだけページング方式でクエリを行います.
5.List、MAPなどの集合オブジェクトが使用済みかどうかをチェックした後、クリアされていない問題.List、MAPなどの集合オブジェクトは常にオブジェクトへの参照が残っており、これらのオブジェクトはGCで回収できない.
手順4では、メモリ表示ツールを使用してメモリの使用状況を動的に表示します.