Javaのメモリリーク問題事例分析

WBOY
リリース: 2023-05-23 18:46:06
転載
1038 人が閲覧しました

Java のメモリ リーク問題

いわゆるメモリ リークとは、プログラムで使用されなくなったオブジェクトまたは変数がメモリ内で占有されていることを意味します。

Java にはガベージ コレクション メカニズムがあり、オブジェクトが参照されなくなったとき、つまりオブジェクトが孤立したとき、オブジェクトはガベージ コレクタによってメモリから自動的にクリアされます。 。

Java にはガベージ コレクション メカニズムがあるのに、メモリ リークの問題が依然として存在するのはなぜでしょうか?

一部のオブジェクトはガベージ コレクターで処理できず、これらのオブジェクトが常に JVM メモリを占有することになり、メモリ リークが発生するだけです。

Java はガベージ コレクション管理に有向グラフを使用するため、参照サイクルの問題を排除できます。たとえば、相互に参照する 2 つのオブジェクトがある場合、それらのオブジェクトにアクセスできない限り、たとえば、次のコードは、この場合のメモリのリサイクルを確認できます。

import java. io.IOException;
public class GarbageTest {

    public static void main(String[] args) throws IOException {
        try {
            // TODO Auto-generated method stub
            gcTest();
        } catch (IOException e) {
            e.printStackTrace();
        }

        System.out.println("has exited gcTest!");
        System.in.read();
        System.in.read();
        System.out.println("out begin gc!");
        for (int i = 0; i < 100; i++) {
            System.gc();
            System.in.read();
            System.in.read();
        }
    }

    private static void gcTest() throws IOException {
        System.in.read();
        System.in.read();
        Person p1 = new Person();
        System.in.read();
        System.in.read();
        Person p2 = new Person();
        p1.setMate(p2);
        p2.setMate(p1);
        System.out.println("before exit gctest!");
        System.in.read();
        System.in.read();
        System.gc();
        System.out.println("exit gctest!");
    }

    private static class Person {
        byte[] data = new byte[20000000];
        Person mate = null;

        public void setMate(Person other) {
            mate = other;
        }
    }

}
ログイン後にコピー

Java でのメモリ リーク: 存続期間の長いオブジェクトが存続期間の短いオブジェクトへの参照を保持している場合、メモリ リークが発生する可能性があります。存続期間の短いオブジェクトはもう必要ありませんが、長命 循環オブジェクトはその参照を保持しており、リサイクルすることはできません。これは、Java でメモリ リークが発生するシナリオです。平たく言えば、プログラマはオブジェクトを作成した後、それを再度使用することはありませんが、オブジェクトは常に使用されます。参照とは、このオブジェクトは役に立たないが、ガベージ コレクターによってリサイクルできないことを意味し、これは Java でメモリ リークが発生する可能性がある状況です。

たとえば、キャッシュ システムでは、オブジェクトをロードしてキャッシュ (たとえば、グローバル マップ オブジェクト内) に置きます。その後、それを再度使用することはありません。このオブジェクトの値は、次の方法で参照されます。キャッシュがありますが、もう使用されませんので、便利にご利用ください。

Java でメモリ リークをチェックするには、プログラムの最後まですべての分岐を実行させてから、オブジェクトが使用されているかどうかを確認する必要があります。使用されていない場合は、そのオブジェクトが使用されていると判断できます。メモリリークです。

外部クラスのインスタンス オブジェクトのメソッドが内部クラスのインスタンス オブジェクトを返す場合、外部クラスのインスタンス オブジェクトが使用されなくなっても、内部クラスのオブジェクトは長期間参照されますが、内部クラスはクラスの Instance オブジェクトの外側に存続するため、この外部クラス オブジェクトはガベージ コレクションされず、これによってもメモリ リークが発生します。

次のコンテンツはインターネットからのものです (主な機能は、スタック内の要素を配列から完全に削除するのではなく、要素をクリアすることですが、保存されている総量を減らすことです。私はこれよりもうまく書くことができます、要素を削除するときは、要素を配列からも消去します。その要素の位置の値を null に設定するだけです)

このスタックより古典的な例は本当に思いつきません。他の人の例を引用しなければなりません。以下の例は私が考えたことではなく、本で見たものです。もちろん、本で見ていなかったとしても、しばらくしてから自分で考えたかもしれません。でもその時は自分で考えたと言いましたが、誰も信じてくれませんでした。

public class Stack {
    private Object[] elements = new Object[10];
    private int size = 0;

    public void push(Object e) {
        ensureCapacity();
        elements[size++] = e;
    }

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

    private void ensureCapacity() {
        if (elements.length == size) {
            Object[] oldElements = elements;
            elements = new Object[2 * elements.length + 1];
            System.arraycopy(oldElements, 0, elements, 0, size);
        }
    }
}
ログイン後にコピー

上記の原則は非常に単純です。お金の山に 10 個の要素が追加され、お金の山は空で欲しいものは何もありませんが、それらがすべてポップアップした場合、これはリサイクルできないオブジェクトです。これはメモリ リークの 2 つの条件を満たします。役に立たないため、リサイクルできません。しかし、たとえそのようなものが存在したとしても、それが必ずしも何らかの結果をもたらすとは限りません。この山積みのお金の使用が減ったとしても、それは数Kのメモリを無駄にするだけです。とにかく、私たちのメモリはすでにGを超えています、それでどのような影響があるでしょうか?それに、これはすぐにリサイクルされるのに、それはどうでもいいのですか?以下に 2 つの例を見てみましょう。

class Bad {
    public static Stack s = new Stack();
    static {
        s.push(new Object());

        s.pop(); //这里有一个对象发生内存泄露

        s.push(new Object());//上面的对象可以被回收了,等于是自愈了
    }
}
ログイン後にコピー

静的であるため、プログラムが終了するまで存在しますが、自己修復機能があることもわかります。つまり、スタックに最大 100 個のオブジェクトがある場合、オブジェクトは最大 100 個しかありません。リサイクルできません。実際、これは簡単に理解できるはずです。スタックは内部的に 100 個の参照を保持します。最悪のシナリオは、それらがすべて役に立たないことです。新しい進捗を追加すると、以前の参照は自然に消えます。

メモリ リークの別の状況: オブジェクトが HashSet コレクションに格納されている場合、ハッシュ値の計算に関与するオブジェクト内のフィールドは変更できません。それ以外の場合、オブジェクトは変更されます。値が異なります。この場合、contains メソッドがオブジェクトの現在の参照をパラメーターとして使用して HashSet コレクションからオブジェクトを取得したとしても、見つからないオブジェクトが返されます。その結果、HashSet コレクションから現在のオブジェクトを個別に削除できなくなり、メモリ リークが発生します。

添付: メモリ リークの典型的な状況

(1) データ構造によって引き起こされる短期的なメモリ リークの問題。次のコードを参照してください。

public class Stack{  
      private Object[] element=new Object[10];  
      private int size=0;  
        
      public void push(Object ele){  
             ensureCapacity();  
             element[size++]=ele;  
      }  
  
      public Object pop(){  
             if(size==0) throw new EmptyStackException();  
             return element[--size]; //短暂造成内存泄露  
      }  
  
      private void ensureCapacity(){  
             if(element.length==size){  
                     Object[] oldElement=element;  
                     element=new Object[size*2+1];  
                     System.arraycopy(oldElement,0,element,0,size);  
             }  
      }  
}
ログイン後にコピー

上記のコードはポップします。 () 毎回 Stack は毎回要素をポップアップします。新しい要素を追加する前に、実際にはポップされたオブジェクトを指す参照 element[x] がまだ存在するため、GC はそれをガベージ コレクションしません。新しい要素をプッシュするときに element[x]=newObject を設定することによってのみ、以前に作成されたオブジェクトをリサイクルできます。上記の Pop() メソッドを次のコードに変更する方がはるかに安全です:

public Object pop(){  
       if(element.length==size) throws EmptyStackException();  
       Object o=element[--size];  
       elements[size]=null;  //使得GC有机会回收这个对象  
       return o;  
}
ログイン後にコピー

以上がJavaのメモリリーク問題事例分析の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:yisu.com
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート