当堆栈中仍然可见时,未使用的对象是否可用于垃圾收集?

在以下示例中,有两个function相同的方法:

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; } } 

但是在它们的第一个(方法1)中,字符串“s1”在return语句之前显然可用于垃圾收集。 在第二个( method2 )字符串“s1”仍然可以访问(虽然从代码审查预期它不再使用)。

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

编辑:有时变量可以像完全渲染的图像一样引用对象,这会对内存产生影响。

我是因为实际考虑而问的。 我在一个方法中有大量内存贪婪的代码,并且只是通过将这个方法分成几个小方法来思考我是否可以帮助JVM(一点点)。

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

更新 :按jls-12.6.1 :

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

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

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

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

然而,在JITC的情况下,即使是稍微优化的人也可能认识到S1未被使用并且回收用于注册S2。 或者它可能不会。 GC将检查寄存器内容,如果S1已被重用于其他内容,则将回收旧的S1对象(如果没有另外引用)。 如果尚未重用S1位置,则可能无法回收S1对象。

“可能不会”,因为根据JVM,JITC可能会也可能不会向GC提供程序流中对象引用“活动”的映射。 并且该地图(如果提供)可以或可以不精确地识别S1的“有效范围”(最后一个参考点)的结束。 许多不同的可能性

请注意,这种潜在的可变性不违反任何Java原则 – GC不需要尽早回收对象,并且没有实际的方法使程序对回收对象时精确敏感。

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

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

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

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

目前尚不清楚。 我认为你把’未使用’与’无法访问’混为一谈。 它们不一定是一回事。

从forms上讲,变量是有效的,直到其封闭范围终止,因此在此之前它不可用于垃圾收集。

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

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

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

您还应该通过JVM进行int帐户优化(如EJP指出)。 还有一个转义分析,它可能会阻止对象进行堆分配。 但依靠你的代码表现是一种不好的做法