在最近的JVM中,不可见的引用仍然是一个问题吗?

我正在阅读Java平台性能(可悲的是,自从我最初提出这个问题以来,链接似乎已经从互联网上消失了)和A.3.3节让我担心。

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

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

我问这个问题是因为论文是从1999年开始的 - 有时事情会发生变化,特别是在GC领域。


由于这篇论文不再可用,我想解释一下这种担忧。该论文暗示,在方法中定义的变量将被视为GC根,直到方法退出,而不是直到代码块结束。因此,必须将变量设置为 null,以允许对引用的对象进行垃圾回收。

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

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

这似乎是这样一种想法的起源,即使变量引用为空将有助于垃圾回收器,即使该变量即将退出范围。


答案 1

此代码应将其清除:

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() 而不是循环不会导致收集对象。


答案 2

问题仍然存在。我用Java 8测试了它,可以证明这一点。

您应注意以下事项:

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

  2. 第1点中描述的保证指导性案例并不保证最终确定。未指定调用 finalize() 方法的时间,可能根本不调用它们。因此,将 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");
            }
        }
    }
    

推荐