1

好的,我看看能不能解释一下。

我有一些代码将 Java 迭代器(来自 Hadoop,碰巧)包装在 Scala Stream 中,因此我无法直接控制的客户端代码可能会多次读取它。使用此 Stream 完成的最后一件事是 reduce() 操作。Stream 会记住它已经看到的所有项目。不幸的是,在某些情况下迭代器会非常大,因此将所有项目存储在其中会导致内存不足错误。但是,一般来说,客户端代码需要多次迭代工具的情况与内存破坏迭代器的情况不同,如果确实存在这种情况,那不是我的问题。

我要确保的是我可以为需要它的代码提供记忆功能,但不能为不需要它的代码提供记忆功能(特别是对于根本不查看 Stream 的代码)。

Stream 中 reduce() 的代码说,它的编写方式允许对 Stream 的已访问部分进行 GC,同时减少。所以如果我能确保这真的发生,我会没事的。但在实践中,我如何确保发生这种情况?特别是,如果函数 A 创建流并将其传递给函数 B,函数 B 将流传递给函数 C,然后函数 C 调用 reduce(),那么函数 A、B 和 C 中仍然对流的引用呢? ? 在所有这些情况下,这三个函数中的任何一个都不会进一步使用流,尽管调用不一定是尾递归的。JVM 是否足够聪明,可以确保在调用 reduce() 时,函数 A、B 和 C 的引用计数为 0,从而可以发生 GC?本质上,这意味着 JVM 在函数 A 中注意到它对项目所做的最后一件事是调用函数 B,因此它在调用 B 的同时消除了自己的句柄,

如果这能正常工作,如果 A、B 或 C 有一个局部变量持有该项目,它是否也能工作?(同样,以后不会使用它。)那是因为在不使用本地变量的情况下正确编码会更加棘手。

4

1 回答 1

2

一个在范围内但永远不会被读取的变量是dead。出于垃圾收集的目的,JVM 可以自由地忽略死变量;仅由死变量指向的对象是无法访问的,并且可以被收集。JLS 的相关位是,不够隐晦的是,§12.6.1 实施终结,其中说:

可达对象是可以从任何活动线程的任何潜在持续计算中访问的任何对象。

并解释说:

可以设计优化程序的转换,将可到达的对象的数量减少到比那些天真地认为是可到达的要少。例如,Java 编译器或代码生成器可能会选择将不再使用的变量或参数设置为 null,以使此类对象的存储可能更快地被回收。

如果对象字段中的值存储在寄存器中,则会出现另一个示例。然后程序可能会访问寄存器而不是对象,并且永远不会再次访问对象。这意味着该对象是垃圾。请注意,仅当引用在堆栈上而不是存储在堆中时才允许进行这种优化。

如果您的方法 A 只有引用流的死变量,那么它不会妨碍其收集。

但是请注意,这意味着局部变量:如果您有引用流的字段(包括来自包含嵌套类的方法的封闭局部变量),那么这不适用;我认为 JVM 不允许将这些视为已死。换句话说,这里:

public Callable<String> foo(final Object o) {
    return new Callable<String>() {
        public String call() throws InterruptedException {
            String s = o.toString();
            Thread.sleep(1000000);
            return s;
        }
    };
}

在收集匿名对象o之前无法收集对象Callable,即使在toString调用后从未使用过它,因为在Callable.

于 2012-09-24T19:36:31.300 回答