据我所知,方法的局部变量位于执行线程的堆栈框架中,局部变量的引用类型仅具有对象的引用,而没有对象本身。 JVM中的所有对象都位于堆空间中。
我想知道被执行方法中的局部变量引用的对象永远不会被垃圾收集,直到方法执行结束为止。 (不使用java.lang.ref.WeakReference和SoftReference。)
它们是垃圾收集吗?还是从来没有?这类东西有编译器的优化吗?
(如果从不对它们进行垃圾收集,这意味着可能需要在执行耗时的大型方法时将null分配给不再使用的变量。)
答案 0 :(得分:0)
并不是所有的对象都在堆空间中;但通常是这样。 Java已扩展为具有堆栈本地对象,条件是JVM可以检测到该对象仅在堆栈帧内有效。
现在显示堆中的对象,这些对象在方法中具有本地引用。在处理方法时,与方法运行关联的堆栈框架包含局部变量引用。只要可以使用引用(包括仍在堆栈帧中),就不会对对象进行垃圾收集。
一旦引用被销毁,并且正在运行的程序无法再访问该对象(因为没有引用可以到达该对象),那么垃圾收集器将对其进行收集。
答案 1 :(得分:0)
如Can java finalize an object when it is still in scope?中所述,局部变量不会阻止对引用对象的垃圾回收。或者,正如this answer所说的,作用域只是一个语言概念,与垃圾回收器无关。
我将再次引用规范的相关部分,JLS §12.6.1:
reachable 对象是可以在任何活动线程中进行的任何潜在连续计算中访问的任何对象。
此外,我将答案示例扩展到
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中添加的方法)。该方法演示了优化程序在各个版本之间变得更好的影响,这对垃圾收集早于想要的问题产生了影响。当然,首选的解决方案是编写完全不依赖垃圾收集时间的代码。