19

在以下示例中,有两种功能等效的方法:

public class Question {

    public static String method1() {
        String s = new String("s1");
        // some operations on s1
        s = new String("s2");
        return s;
    }

    public static String method2() {
        final String s1 = new String("s1");
        // some operations on s1
        final String s2 = new String("s2");
        return s2;
    }
}

但是在其中的 first( method1) 中,字符串“s1”显然可用于return声明前的垃圾收集。在 second( method2) 中,字符串“s1”仍然可以访问(尽管从代码审查的角度来看,它不再使用了)。

我的问题是 - jvm 规范中是否有任何内容表明一旦变量在堆栈中未使用,它就可以用于垃圾收集?

编辑: 有时变量可以引用像完全渲染的图像这样的对象并且对内存有影响。

我问是出于实际考虑。我在一种方法中有大量内存贪婪的代码,我想我是否可以通过将这种方法分成几个小的方法来帮助 JVM(一点)。

我真的更喜欢没有重新分配的代码,因为它更容易阅读和推理。

更新:根据jls-12.6.1

Java 编译器或代码生成器可能会选择将不再用于 null 的变量或参数设置为导致此类对象的存储可能更快地被回收

所以看起来 GC 有可能声明仍然可见的对象。但是我怀疑这种优化是在离线编译期间完成的(它会搞砸调试)并且很可能会由 JIT 完成。

4

5 回答 5

5

不,因为可以想象你的代码可以检索它并用它做一些事情,而抽象 JVM 不考虑前面的代码是什么。然而,一个非常、非常、非常聪明的优化 JVM 可能会提前分析代码并发现s1无法引用,然后将其垃圾收集。不过,你绝对不能指望这一点。

于 2013-11-11T00:13:06.983 回答
4

如果您在谈论解释器,那么在第二种情况下,S1 保持“引用”状态,直到方法退出并且堆栈帧被卷起。(也就是说,在标准解释器中——GC 完全有可能使用来自方法验证的活性信息。此外(而且更有可能),javac 可以进行自己的活性分析并基于此“共享”解释器插槽。 )

然而,在 JITC 的情况下,即使是轻微优化的人也可能会认识到 S1 未使用并为 S2 回收该寄存器。或者它可能不会。GC 将检查寄存器内容,如果 S1 已被用于其他用途,则旧的 S1 对象将被回收(如果没有以其他方式引用)。如果 S1 位置没有被重用,那么 S1 对象可能不会被回收。

“可能不会”,因为根据 JVM,JITC 可能会也可能不会向 GC 提供对象引用在程序流中“活动”的位置的映射。如果提供这张地图,可能会也可能不会准确识别 S1 的“生命范围”(最后一个参考点)的终点。许多不同的可能性。

请注意,这种潜在的可变性并不违反任何 Java 原则——GC 不需要尽早回收对象,并且没有实用的方法可以让程序准确地知道对象何时被回收。

于 2013-11-11T01:29:16.350 回答
1

VM 可以在方法退出之前自由优化代码以使其无效s1(只要它是正确的),因此s1可能更早有资格获得垃圾。

然而,这几乎没有必要。在下一次 GC 之前必须发生许多方法调用;无论如何,所有堆栈帧都已清除,无需担心特定方法调用中的特定局部变量。

就 Java 语言而言,垃圾可以永远存在而不会影响程序语义。这就是为什么 JLS 几乎不谈论垃圾的原因。

于 2013-11-11T00:37:40.330 回答
0

在其中的第一个字符串“s1”显然可用于返回语句之前的垃圾收集

根本不清楚。我认为您将“未使用”与“无法访问”混淆了。它们不一定是同一件事。

正式地说,该变量在其封闭范围终止之前是活动的,因此在此之前它不能用于垃圾收集。

但是,“Java 编译器或代码生成器可能会选择将不再用于 null 的变量或参数设置为导致此类对象的存储可能更快地被回收” JLS #12.6.1

于 2013-11-11T00:31:18.617 回答
0

基本上堆栈帧和静态区域被 GC 视为根。因此,如果从任何堆栈帧引用对象,则它被认为是活动的。从活动堆栈帧中回收一些对象的问题是 GC 与应用程序(mutator)并行工作。你认为 GC 应该如何在方法进行时发现对象未被使用?这将需要一个非常繁重和复杂的同步,实际上这将打破 GC 与 mutator 并行工作的想法。每个线程都可能将变量保存在处理器寄存器中。要实现您的逻辑,还应该将它们添加到 GC 根。我什至无法想象如何实现它。

回答你的问题。如果您有任何逻辑会产生大量将来未使用的对象,请将其分离为不同的方法。这实际上是一个很好的做法。

您还应该考虑 JVM 的 int 帐户优化(如 EJP 指出的那样)。还有一个逃逸分析,它可能完全阻止对象进行堆分配。但是将您的代码性能依赖于它们是一种不好的做法

于 2013-11-11T06:46:08.657 回答