隐形引用在最近的JVM中仍然是一个问题吗?

时间:2008-11-07 09:25:47

标签: java memory-leaks garbage-collection jvm sun

我正在阅读 Java Platform Performance (遗憾的是,自从我最初提出这个问题以来,链接似乎已从互联网上消失了),A.3.3节让我很担心。

我一直在假设退出范围的变量不再被视为GC根,但本文似乎与此相矛盾。

最近的JVM,特别是Sun的1.6.0_07版本,是否还有这个限制?如果是这样,那么我有很多代码要分析......

我问这个问题是因为这篇论文是从1999年开始的 - 有时情况会发生变化,特别是在GC的世界里。


由于论文不再可用,我想解释一下这个问题。本文暗示,在方法退出之前,在方法内定义的变量将被视为GC根,而不是直到代码块结束。因此,必须将变量设置为null,以允许引用的Object被垃圾回收。

这意味着main()方法中的条件块中定义的局部变量(或包含无限循环的类似方法)会导致一次性内存泄漏,除非在它退出范围之前将变量置为空

chosen answer的代码很好地说明了这个问题。在文档中引用的JVM版本中,当foo对象在try块结束时超出范围时,不能对其进行垃圾回收。相反,JVM将保持打开引用直到main()方法结束,即使任何东西都不可能使用该引用。

这似乎是将变量引用置零有助于垃圾收集器输出的想法的起源,即使该变量刚刚超出范围。

4 个答案:

答案 0 :(得分: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()而不是循环不会导致出于某种原因收集对象。

答案 1 :(得分:2)

文章指出:

  

......一个有效的实施   JVM不太可能将引用归零   当它超出范围时

我认为这是因为这样的情况:

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

这里,在String s的每个声明中,“高效JVM”使用相同的引用,但如果GC没有启动,则堆中将有10个新的字符串。

在文章示例中,我认为对foo的引用保留在堆栈中,因为“高效JVM”认为很可能会创建另一个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 - &gt; b被收集

teste2 - &gt; b是不可见的

teste3 - &gt; b是不可见的

teste4 - &gt; b被收集

...所以我认为,在循环中,JVM在循环结束时不会创建不可见的变量,因为它们不太可能在循环之外再次声明。

任何想法??

答案 2 :(得分:1)

您真的需要分析那么多代码吗?基本上我只能看到这对于长时间运行的方法来说是一个重要的问题 - 通常只是每个线程堆栈顶部的方法。

如果目前尚未解决,我不会感到惊讶,但我认为这可能不会像您担心的那样重要。

答案 3 :(得分:1)

问题仍然存在。我用Java 8测试它并且可以证明它。

您应该注意以下事项:

  1. 强制保证垃圾收集的唯一方法是尝试以OutOfMemoryError结尾的分配,因为在抛出之前需要JVM尝试释放未使用的对象。然而,如果请求的数量太大而不能成功,即过度地址空间,则这不成立。在获得OOME之前尝试提高分配是一个很好的策略。

  2. 第1点中描述的保证GC不保证最终确定。未指定调用finalize()方法的时间,根本不会调用它们。因此,向类中添加finalize()方法可能会阻止其实例的收集,因此最终确定不是分析GC行为的好选择。

  3. 在局部变量超出范围后创建另一个新的局部变量将重用其在堆栈帧中的位置。在下面的示例中,将收集对象a,因为它在堆栈框架中的位置被局部变量b占用。但是b一直持续到main方法结束,因为没有其他局部变量占据它的位置。

    import java.lang.ref.*;
    
    public class Test {
        static final ReferenceQueue<Object> RQ=new ReferenceQueue<>();
        static Reference<Object> A, B;
        public static void main(String[] s) {
            {
                Object a=new Object();
                A=new PhantomReference<>(a, RQ);
            }
            {
                Object b=new Object();
                B=new PhantomReference<>(b, RQ);
            }
            forceGC();
            checkGC();
        }
    
        private static void forceGC() {
            try {
                for(int i=100000;;i+=i) {
                  byte[] b=new byte[i];
                }
            } catch(OutOfMemoryError err){ err.printStackTrace();}
        }
    
        private static void checkGC() {
            for(;;) {
                Reference<?> r=RQ.poll();
                if(r==null) break;
                if(r==A) System.out.println("Object a collected");
                if(r==B) System.out.println("Object b collected");
            }
        }
    }