这个问题在这里已有答案:
据我所知,方法的局部变量位于执行线程的堆栈帧中,局部变量的引用类型只有对象的引用,而不是对象本身。 JVM中的所有对象都位于堆空间中。
我想知道正在执行的方法中的局部变量引用的对象永远不会被垃圾收集,直到方法执行结束。 (不使用java.lang.ref.WeakReference和SoftReference。)
他们是垃圾收集?或者从不?是否有编译器优化这种类型的东西?
(如果它们从不被垃圾收集,这意味着可能需要将null赋给执行大型方法时不再使用的变量,这些变量需要很长时间。)
正如在Can java finalize an object when it is still in scope?中详细描述的那样,局部变量不会阻止引用对象的垃圾收集。或者,正如this answer所说,范围只是一种语言概念,与垃圾收集器无关。
我将再次引用规范的相关部分JLS §12.6.1:
可到达对象是可以从任何活动线程的任何潜在继续计算中访问的任何对象。
此外,我将答案的例子扩展到了
class A {
static volatile boolean finalized;
Object b = new Object() {
@Override protected void finalize() {
System.out.println(this + " was finalized!");
finalized = true;
}
@Override public String toString() {
return "B@"+Integer.toHexString(hashCode());
}
};
@Override protected void finalize() {
System.out.println(this + " was finalized!");
}
@Override public String toString() {
return super.toString() + " with "+b;
}
public static void main(String[] args) {
A a = new A();
System.out.println("Created " + a);
for(int i = 0; !finalized; i++) {
if (i % 1_000_000 == 0)
System.gc();
}
System.out.println("finalized");
}
}
Created A@59a6e353 with B@6aaa5eb0
B@6aaa5eb0 was finalized!
finalized
A@59a6e353 with B@6aaa5eb0 was finalized!
这表明甚至具有范围变量的方法也可以检测引用对象的最终化。此外,从堆变量引用也不一定阻止垃圾收集,因为B
对象是不可达的,因为当包含引用的对象也不可访问时,没有连续计算可以访问它。
值得强调的是,即使使用该对象也不总是阻止其垃圾收集。重要的是,对象的内存是否是正在进行的操作所需要的,并且源代码中对对象字段的每次访问都不得在运行时导致实际的内存访问。规范说明:
可以设计优化程序的转换,以减少可达到的对象的数量,使其少于可以被认为可达的对象的数量。 [...]
如果对象字段中的值存储在寄存器中,则会出现另一个示例。然后程序可以访问寄存器而不是对象,并且永远不会再次访问对象。这意味着该对象是垃圾。
这不仅是一个理论选择。正如在finalize() called on strongly reachable object in Java 8中所讨论的那样,当对象调用它们时,它甚至可能发生在对象上,换句话说,当实例方法仍在执行时,this
引用可能会被垃圾收集。
防止对象垃圾收集的唯一方法是,如果终结器也在对象上进行同步或调用Reference.reachabilityFence(object)
(Java 9中添加的方法),则在对象上进行同步。后期添加fence方法演示了优化器的影响在早期的垃圾收集问题上,从版本到版本越来越好。当然,首选的解决方案是编写完全不依赖于垃圾收集时间的代码。
所有对象都在堆空间中并不完全正确;但这通常是正确的。 Java已经扩展为具有堆栈本地对象,前提是JVM可以检测到对象只能在堆栈帧中存活。
现在为堆上的对象,它们在方法中具有本地引用。在处理该方法时,与方法run相关联的堆栈帧包含局部变量引用。只要可以使用引用(包括仍然在堆栈帧中),就不会对对象进行垃圾回收。
一旦引用被销毁,并且正在运行的程序无法再访问该对象(因为没有可以访问它的引用),那么垃圾收集器将收集它。