24

私はJava Platform Performanceを読んでいました (悲しいことに、最初にこの質問をしたときからリンクがインターネットから消えたようです)、セクション A.3.3 が心配でした。

私は、スコープから外れた変数はもはや GC ルートとは見なされないだろうという仮定に取り組んでいましたが、この論文はそれと矛盾しているようです。

最近の JVM、特に Sun の 1.6.0_07 バージョンにはまだこの制限がありますか? もしそうなら、分析するコードがたくさんあります...

この論文は 1999 年のものであるため、質問します。特に GC の世界では、状況が変わることがあります。


論文が入手できなくなったので、懸念を言い換えたいと思います。この論文は、メソッド内で定義された変数は、コード ブロックが終了するまでではなく、メソッドが終了するまで GC ルートと見なされることを暗示していました。したがって、変数を null に設定して、参照されるオブジェクトをガベージ コレクションできるようにする必要がありました。

つまり、main() メソッド (または無限ループを含む同様のメソッド) の条件付きブロックで定義されたローカル変数は、変数がスコープから外れる直前にその変数を null にしない限り、1 回限りのメモリ リークを引き起こします。

選択した回答のコードは、問題をよく示しています。このドキュメントで参照されている JVM のバージョンでは、foo オブジェクトが try ブロックの最後でスコープ外にドロップされた場合、ガベージ コレクションを実行できません。代わりに、JVM は main() メソッドが終了するまで参照を開いたままにしますが、その参照を使用することは不可能です。

これは、変数がちょうどスコープから外れようとしている場合でも、変数参照を null にすることがガベージ コレクターの助けになるという考えの起源のようです。

4

4 に答える 4

6

このコードはそれをクリアする必要があります:

public class TestInvisibleObject{
  public static class PrintWhenFinalized{
    private String s;
    public PrintWhenFinalized(String s){
      System.out.println("Constructing from "+s);
      this.s = s;
    }
    protected void finalize() throws Throwable {
      System.out.println("Finalizing from "+s);
    }   
  }
  public static void main(String[] args) {
    try {
        PrintWhenFinalized foo = new PrintWhenFinalized("main");
    } catch (Exception e) {
        // whatever
    }
    while (true) {
      // Provoke garbage-collection by allocating lots of memory
      byte[] o = new byte[1024];
    } 
  }
}

私のマシン(jdk1.6.0_05)では、次のように出力されます。

メインから構築

メインからのファイナライズ

したがって、問題は修正されたようです。

ループの代わりにSystem.gc()を使用しても、何らかの理由でオブジェクトが収集されないことに注意してください。

于 2008-11-07T11:33:53.737 に答える
2

この記事では、次のように述べています。

... JVM の効率的な実装では、範囲外になったときに参照がゼロになる可能性は低い

これは、次のような状況が原因で発生すると思います。

public void doSomething() {  
    for(int i = 0; i < 10 ; i++) {
       String s = new String("boo");
       System.out.println(s);
    }
}

ここでは、String の各宣言で「効率的な JVM」によって同じ参照が使用されますが、GC が開始されない場合、ヒープには 10 個の新しい String が存在します。

この記事の例では、「効率的な JVM」は別の foo オブジェクトが作成される可能性が非常に高く、その場合は同じ参照を使用する可能性が高いと考えているため、foo への参照はスタックに保持されていると思います。感想???

public void run() {
    try {
        Object foo = new Object();
        foo.doSomething();
    } catch (Exception e) {
        // whatever
    }
    while (true) { // do stuff } // loop forever
}

プロファイリングを使用して次のテストも実行しました。

public class A {

    public static void main(String[] args) {
        A a = new A();  
        a.test4();
    }

    public void test1() {  
        for(int i = 0; i < 10 ; i++) {
           B b = new B();
           System.out.println(b.toString());
        }
        System.out.println("b is collected");
    }

    public void test2() {
        try {
            B b = new B();
            System.out.println(b.toString());
        } catch (Exception e) {
        }
        System.out.println("b is invisible");
    }

    public void test3() {
        if (true) {
            B b = new B();
            System.out.println(b.toString());
        }
        System.out.println("b is invisible");
    }

    public void test4() {
        int i = 0;
        while (i < 10) {
            B b = new B();
            System.out.println(b.toString());
            i++;
        }
        System.out.println("b is collected");
    }

    public A() {
    }

    class B {
        public B() {
        }

        @Override
        public String toString() {
            return "I'm B.";
        }
    }
}

そして結論に達します:

teste1 -> b が収集されます

teste2 -> b は見えない

teste3 -> b は見えない

teste4 -> b が収集されます

...ループでは、ループの外側で再び宣言される可能性は低いため、ループが終了したときにJVMは目に見えない変数を作成しないと思います。

何かご意見は??

于 2008-11-07T12:34:19.310 に答える
1

分析するコードが本当にたくさんありますか?基本的に、これは非常に長時間実行されるメソッド(通常は各スレッドのスタックの最上位にあるメソッド)にとって重大な問題であることがわかります。

現時点で修正されていなくてもまったく驚かないでしょうが、あなたが恐れているほど重要ではないと思います。

于 2008-11-07T09:42:49.763 に答える