什么时候Java本地变量符合GC的条件?

时间:2009-09-01 15:23:30

标签: java

鉴于以下计划:

import java.io.*;
import java.util.*;

public class GCTest {

    public static void main(String[] args) throws Exception {
        List cache = new ArrayList();
        while (true) {
            cache.add(new GCTest().run());
            System.out.println("done");
        }
    }

    private byte[] run() throws IOException {
        Test test = new Test();
        InputStream is = test.getInputStream();
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] buff = new byte[256];
        int len = 0;
        while (-1 != (len = is.read())) {
            baos.write(buff, 0, len);
        }
        return baos.toByteArray();
    }

    private class Test {
        private InputStream is;

        public InputStream getInputStream() throws FileNotFoundException {
            is = new FileInputStream("GCTest.class");
            return is;
        }

        protected void finalize() throws IOException {
            System.out.println("finalize");
            is.close();
            is = null;
        }
    }
}

当run方法中的while循环仍在执行且局部变量test仍在范围内时,你会期望调用finalize吗?

更重要的是,这种行为是在任何地方定义的吗? Sun是否有任何声明它是实现定义的?

这与之前询问此问题的方式相反,人们主要关注的是内存泄漏问题。在这里,我们让GC积极地GCing我们仍然感兴趣的变量。你可能会期望,因为测试仍在“范围内”,它不会是GC。

对于记录,似乎有时测试“有效”(即最终命中OOM),有时它会失败,具体取决于JVM的实现。

不要捍卫这段代码的编写方式BTW,这只是一个工作中出现的问题。

6 个答案:

答案 0 :(得分:14)

虽然如果对象仍在范围内,它将不会被垃圾收集,但如果该代码中没有实际使用该变量,则JIT编译器可能会将其从范围中取出(因此您看到的行为不同)即使你读取源代码,变量似乎仍然“在范围内”。

如果您不再在代码中引用对象,我不明白为什么您关心对象是垃圾收集,但是如果您想确保对象留在内存中,最好的方法是直接在字段中引用它们一个类,甚至更好的静态字段。如果静态字段引用该对象,则不会收集垃圾。

编辑:Here是您要查找的明确文档。

  

>我假设一个对象在之前无法死亡对它的本地引用已超出范围。

     

这不能假设。不是   Java规范和JVM规范保证   此

     

仅仅因为变量在范围内,   并不意味着它指向的对象   可以到达。通常是这种情况   一个物体指向的物体   范围内变量是可达的,但是   你的情况并非如此。该   编译器可以在jit时确定   哪些变量是死的而不是   在oop-map中包含这些变量。   由于“nt”指向的对象   可以[原文如此 - 应该不能]   从任何实时变量到达,它是   有资格收藏。

答案 1 :(得分:8)

我建议您和您的同事阅读The Truth About Garbage Collection

一开始,它说:

  

Java的规范   平台很少有人承诺   垃圾收集实际上如何工作。 [消隐]

     

虽然看起来令人困惑,但事实   垃圾收集模型是   实际上并没有严格定义   重要且有用的 - 严格定义的   垃圾收集模型可能是   不可能全部实施   平台。同样,它可能   排除有用的优化和伤害   平台的性能   长期。

在您的示例中,test循环中while变量变为“不可见”(参见上面的A.3.3)。此时,一些JVM将继续将变量视为包含“硬引用”,而其他JVM将视为变量已被置零。这两种行为都适用于兼容的JVM

引自JLS第3版(第12.6.1节第2段):

  

可达对象是任何对象   可以在任何潜力中访问   从任何现场继续计算   线。

请注意,可访问性根本没有根据范围定义。引用文本如下:

  

优化转换   可以设计一个减少的程序   对象的数量   可达到小于那些   天真地被认为是可达的。   例如,编译器或代码   生成器可以选择设置变量   或者不再是的参数   用于null以导致存储   这样的对象是潜在的   

(我的重点补充说。)这意味着对象对象可能被垃圾收集,并且可能比您预期的更早或更晚地进行终结。值得注意的是,在完成无法访问的对象之前,某些JVM需要多个GC周期。

最重要的是,依赖于早期或晚期发生的终结的程序本质上是不可移植的,并且在我看来是错误的。

答案 2 :(得分:3)

稍微偏离主题,但finalize()永远不应该用于关闭()文件。该语言保证finalize()将被调用。始终使用try ... finally结构来保证文件关闭,数据库清理等等。

答案 3 :(得分:1)

你有什么观察到你觉得奇怪?每次执行run()时,都会创建一个新的Test实例。运行完成后,该测试实例超出范围并符合垃圾回收条件。当然“符合垃圾收集条件”和“垃圾收集”并不是一回事。我希望如果你运行这个程序,你会看到一堆终结消息作为run complete的调用滚动。作为我看到的唯一控制台输出是这些消息,我看不出当你看到每条消息时你怎么知道哪个Test实例正在最终确定。如果在每次运行调用开始时添加了println,可能会获得更有趣的结果,甚至可能会为Test对象添加一个计数器,每次创建一个新对象时都会递增,并使用finalize消息输出。然后你可以看到真正发生的事情。 (好吧,也许你用调试器来运行它,但这也可能会模糊不清。)

答案 4 :(得分:0)

由于test仅使用一次,因此可以在调用后立即将其删除。即使每次调用read都使用getInputStream调用而不是使用本地is变量,也可以优化对象的使用。由于使用了锁定,FIleInputStream无法提前完成。终结者很难。

无论如何,你的终结者毫无意义。无论如何,基础FileInputStream将在最终确定时关闭。

答案 5 :(得分:0)

理论上,测试不能在范围内,因为它在方法级别run(),并且当你从方法中出来时应该对局部变量进行垃圾收集。但是你将结果存储在列表中,我有读取它的某些列表很容易存储不易被垃圾收集的弱引用(取决于jvm实现)。