在以下示例中,有两个功能相同的方法:
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;
}
}
但是在它们的第一个(method1
)中,字符串“s1”在return
语句之前显然可用于垃圾收集。在第二个(method2
)字符串“s1”仍然可以访问(虽然从代码审查预期它不再使用)。
我的问题是 - 在jvm规范中是否有任何内容表明,一旦变量在堆栈中未使用,它就可用于垃圾收集?
修改 的 有时变量可以像完全渲染的图像一样引用对象,并且会对内存产生影响。
我是因为实际考虑而问的。我在一个方法中有大量内存贪婪的代码,并且只是通过将这个方法分成几个小方法来思考是否可以帮助JVM(一点点)。
我更喜欢没有重新分配的代码,因为它更容易阅读和推理。
更新 :每jls-12.6.1:
Java编译器或代码生成器可以选择设置一个不再用于null的变量或参数,以使此类对象的存储可能更快地回收
因此看起来GC可以声明仍然可见的对象。我怀疑,然而这种优化是在离线编译期间完成的(它会搞砸调试),很可能是由JIT完成的。
答案 0 :(得分:4)
不,因为您的代码可以想象地检索它并对其执行某些操作,并且抽象JVM不会考虑将要使用的代码。但是,一个非常非常非常聪明的优化JVM可能会分析前面的代码并发现无法引用s1
,并且垃圾收集它。但你绝对不能指望这一点。
答案 1 :(得分:4)
如果您正在谈论解释器,那么在第二种情况下,S1保持“引用”,直到该方法退出并且堆栈帧被卷起。 (也就是说,在标准解释器中 - GC完全有可能使用来自方法验证的活跃信息。而且,此外(并且更有可能),javac可以进行自己的活跃度分析并基于此分享“共享”解释器插槽。 )
然而,在JITC的情况下,即使是稍微优化的人也可能认识到S1未使用并且循环注册S2。或者它可能不会。 GC将检查寄存器内容,如果S1已被重用于其他内容,则将回收旧的S1对象(如果没有另外引用)。如果尚未重用S1位置,则可能不会回收S1对象 。
“可能不会”,因为根据JVM,JITC可能会也可能不会向GC提供对象引用在程序流中“活动”的位置的映射。并且该地图(如果提供)可以或可以不精确地识别S1的“有效范围”(最后一个参考点)的结束。许多不同的可能性。
请注意,这种潜在的可变性不违反任何Java原则 - GC不需要尽早回收对象,并且没有实际的方法让程序对回收对象时精确敏感。 / p>
答案 2 :(得分:1)
VM可以在方法退出之前自由优化代码以使s1
无效(只要它是正确的),因此s1
可能更早符合垃圾条件。
然而,这几乎是不必要的。许多方法调用必须在下一个GC之前发生;无论如何,所有堆栈帧都已被清除,无需担心特定方法调用中的特定局部变量。
就Java语言而言,垃圾可以永久存在而不会影响程序语义。这就是JLS几乎没有谈论垃圾的原因。
答案 3 :(得分:0)
在其中第一个字符串“s1”显然可用于返回语句之前的垃圾收集
根本不清楚。我认为你将“未使用”与“无法访问”混为一谈。他们不一定同样的事情。
从形式上讲,变量是有效的,直到其封闭范围终止,因此在此之前它不可用于垃圾收集。
然而,“Java编译器或代码生成器可能会选择设置一个不再用于null的变量或参数,以使此类对象的存储可能更快地回收”JLS #12.6.1。
答案 4 :(得分:0)
基本上堆栈帧和静态区域被GC视为根。因此,如果从任何堆栈帧引用对象,则认为它是活动的。从活动堆栈帧中回收一些对象的问题是GC与应用程序(mutator)并行工作。您如何看待GC在方法进行过程中发现该对象未被使用?这将需要一个非常繁重和复杂的同步,事实上这将打破GC与mutator并行工作的想法。每个线程都可以将变量保存在处理器寄存要实现您的逻辑,还应将它们添加到GC根目录中。我甚至无法想象如何实现它。
回答你的问题。如果您有任何逻辑产生许多将来未使用的对象,请将其分离为不同的方法。这实际上是一种很好的做法。
您还应该通过JVM进行int帐户优化(如EJP指出)。还有一个转义分析,可能会阻止对象进行堆分配。但依靠你的代码表现是一种不好的做法