JVMメモリリーク仮想マシンメモリリークまとめ

4903 ワード

メモリ漏洩memory leakとは、プログラムがメモリを申請した後、申請したメモリ空間を解放することができず、一度のメモリ漏洩の危害は無視できるが、メモリ漏洩の堆積結果は深刻で、どれだけのメモリでも遅かれ早かれ光を占めている.
一.発生した方法で分類すると、メモリ漏洩は4種類に分けられます.
1.多発性メモリ漏れ.メモリ漏洩が発生したコードは複数回実行され、実行されるたびにメモリ漏洩が発生します. 
2.偶発メモリの漏洩.メモリ漏洩が発生するコードは、特定の環境または操作中にのみ発生します.常発性と偶発性は相対的である.特定の環境では、偶発的なものが常発的になるかもしれません.そのため、テスト環境とテスト方法はメモリの漏洩を検出するのに重要です. 
3.使い捨てメモリ漏れ.メモリ漏洩が発生したコードは一度しか実行されないか、アルゴリズムの欠陥により、常に1つのメモリが1つだけ漏洩します.たとえば、クラスのコンストラクション関数にメモリを割り当てても、コンストラクション関数ではメモリが解放されないため、メモリ漏洩は一度だけ発生します. 
4.暗黙的なメモリ漏洩.プログラムは実行中にメモリを割り当て続けたが、終了するまでメモリを解放しなかった.厳密には、最終プログラムがすべての申請のメモリを解放したため、メモリ漏洩は発生していません.しかし、1つのサーバ・プログラムでは、数日、数週間、数ヶ月実行する必要があり、メモリをタイムリーに解放しないと、最終的にシステムのすべてのメモリが消費される可能性があります.したがって、このようなメモリ漏洩を暗黙的なメモリ漏洩と呼ぶ. 
二.Javaメモリ漏洩の原因
メモリ漏洩とは、不要なオブジェクトです.(使用されなくなったオブジェクト)メモリや不要なオブジェクトを占有し続けるメモリがタイムリーに解放されないため、メモリ容量の浪費をメモリリークと呼ぶ.メモリリークは深刻で気づきにくい場合があり、開発者はメモリリークの存在を知らないが、深刻な場合もあり、Out of memoryを提示する.Javaメモリリークの根本的な原因は何なのか?ロングライフサイクルのオブジェクトがショートライフサイクルオブジェクトのリファレンスを持っているとメモリ漏洩が発生する可能性があります.ショートライフサイクルオブジェクトはもう必要ありませんが、ロングライフサイクルオブジェクトがリファレンスを持っているため回収できません.これがjavaのメモリ漏洩の発生シーンです.具体的には主に以下のような種類があります.
1、静的集合クラスによるメモリリーク(staticオブジェクトはGCで回収されない):
HashMap、Set、ArrayList、Vectorなどの集合クラスは、メモリ漏洩の一般的な場所です.彼らをstaticと宣言すると、メインプログラムと同じライフサイクルを持ち、中に大量のオブジェクト(参照の関係)が配置されている場合、これらのオブジェクトが使用されていない場合、HashMap、コレクションなどがグローバルなstaticタイプであるため、ゴミ回収は使用されていないオブジェクトを処理することができず、メモリが漏洩します.
public class MemoryLeak{
	static List list = new ArrayList();
	
	public static void main(String[] args) 
			throws InterruptedException{
		
		for(int i =0;i<1000000;i++){
			Student stu = new Student();
			list.add(stu);    //    ,    
		}
		//           
		while(true){
			Thread.sleep(20000);
		}
	}
}
class Student{	
}

2、コレクション内のオブジェクト属性が変更された後、remove()メソッドを呼び出すと機能しません.
public class MemoryLeak2 {
	public static void main(String[] args) {

		Set set = new HashSet(); 
		Person p1 = new Person("  ","pwd1",25); 
		Person p2 = new Person("   ","pwd2",26); 
		Person p3 = new Person("   ","pwd3",27); 
		set.add(p1); 
		set.add(p2); 
		set.add(p3);//  set   3   ! 
		p3.setAge(2); //  p3   ,  p3 hashcode   
		set.remove(p3); //  remove  ,      
		set.add(p3); //    ,  ,  set   4   
	}

}
class Person{
    public Person(){}
    public Person(String name,String pwd,Integer age){
        ...
}

3、各種接続
例えば、データベース接続(dataSource.getConnection())、ネットワーク接続(socket)、io接続、closeが明示的に呼び出されない限り()メソッドは、その接続をオフにします.そうしないと、自動的にGCによって回収されません.ResultsetオブジェクトとStatementオブジェクトについては明示的に回収されなくてもよいですが、Connectionはいつでも明示的に回収できないため、Connectionは自動的に回収されません.Connectionが回収されると、ResultsetオブジェクトとStatementオブジェクトはすぐにNULLになります.ただし、接続プールを使用する場合は、違います.接続を明示的に閉じるだけでなく、Resultset Statementオブジェクトを明示的に閉じる必要があります(そのうちの1つを閉じると、もう1つも閉じます).そうしないと、大量のStatementオブジェクトが解放されず、メモリが漏洩します.この場合、tryで行った接続はfinallyで解放されます.
4、単例モード
単一のオブジェクトが外部オブジェクトの参照を持っている場合、この外部オブジェクトはjvmで正常に回収されず、メモリ漏洩が単一のモードを正しく使用しないことは、メモリ漏洩を引き起こす一般的な問題です.一例のオブジェクトは、初期化された後、JVMのライフサイクル全体にわたって存在します(静的変数として)、一例のオブジェクトが外部オブジェクトの参照を持っている場合、この外部オブジェクトはjvmによって正常に回収されず、メモリが漏洩します.次の例を考慮してください.


class A{
    public A(){
        B.getInstance().setA(this);
    }
    ....
}

//B       
class B{
    private A a;
    private static B instance=new B();
    public B(){}
    public static B getInstance(){
        return instance;
    }
    public void setA(A a){
        this.a=a;
    }
    //getter...
}

5、リードストリームが閉じていない
開発中にストリームを閉じるのを忘れがちなため、メモリの漏洩が発生します.各ストリームはオペレーティングシステムのレベルで開いているファイルハンドルに対応しているため、ストリームが閉じられていないため、オペレーティングシステムのファイルハンドルが常に開いている状態になり、jvmはオペレーティングシステムが開いているファイルハンドルを追跡するためにメモリを消費します.
6、リスナー
Javaプログラミングでは、リスナーと付き合う必要があります.通常、1つのアプリケーションでは多くのリスナーが使用されます.addXXXXListener()などのコントロールを呼び出してリスナーを追加しますが、オブジェクトを解放するときにこれらのリスナーを削除することを覚えていないことが多く、メモリの漏洩の機会が増加します.
7、Stringのintern方法
大きな文字列でStringを呼び出す.intern()メソッドでは、intern()はjvmのメモリプール(PermGen)にStringを配置しますが、jvmのメモリプールはgcにされません.したがって、大きな文字列がintern()メソッドを呼び出すと、gcができないメモリが大量に発生し、メモリが漏洩します.大きな文字列のinternメソッドを使用する必要がある場合は、-X:Max:MaxPermSizeパラメータを使用してPermGenメモリのサイズを調整する必要があります(仮想マシンメモリサイズの設定については、関連ブログを引き続き公開します).
8.hashCode()およびequals()メソッドを実装していないオブジェクトをHashSetに追加する
これは簡単だがよくあるシーンだ.通常、Setは重複するオブジェクトをフィルタリングしますが、hashCode()とequals()が実装されていない場合、重複するオブジェクトはSetに追加され続け、削除する機会はありません.したがってクラスにhashCode()とequals()メソッドを加えることは良いプログラミング習慣である.この機能はLombokの@EqualsAndHashCodeで簡単に実現できます.
三.メモリ漏洩の検出方法
3.1 gcログの記録
jvmパラメータに-verbose:gcを指定することで、メモリの使用を分析するためにgcごとの詳細を記録できます.
3.2 profilingを行う
Visual VMまたはjdkに付属のJava Mission Controlでメモリ解析を行います.
3.3コード審査
コードレビューと静的コードチェックにより、メモリ漏洩の問題を引き起こすエラーコードが検出されました.
四.まとめ
コードレベルのチェックは、一部のメモリ漏洩の問題を発見するのに役立ちますが、本番環境でのメモリ漏洩は、大きな同時シーンで発生する問題が多いため、早期に発見することは容易ではありません.そのため、圧力テストツールで圧力テストを行い、潜在的なメモリ漏れの問題を事前に発見する必要があります.