JVM 是否会垃圾收集不再使用的局部变量引用的对象?

Par*_*olu 6 java garbage-collection jvm

据我所知,方法的局部变量位于执行线程的堆栈帧中,局部变量的引用类型仅具有对象的引用,而不具有对象本身。JVM中的所有对象都位于堆空间中。

我想知道正在执行的方法中局部变量引用的对象在方法执行结束之前永远不会被垃圾收集。(不使用 java.lang.ref.WeakReference 和 SoftReference。)

它们被垃圾收集了吗?或者从来没有?编译器对这类东西有优化吗?

(如果它们从未被垃圾回收,这意味着在执行需要很长时间的大方法时可能需要将 null 分配给不再使用的变量。)

Hol*_*ger 4

正如Java 能否在对象仍在作用域内时终结对象中详细说明的那样?,局部变量不会阻止引用对象的垃圾回收。或者,正如这个答案所说,范围只是一个语言概念,与垃圾收集器无关。

\n\n

我\xe2\x80\x99ll再次引用规范的相关部分,JLS\xc2\xa712.6.1 :

\n\n
\n

可到达对象是可以在任何潜在的持续计算中从任何活动线程访问的任何对象。

\n
\n\n

此外,我将answer\xe2\x80\x99s 示例扩展为

\n\n
class A {\n    static volatile boolean finalized;\n\n    Object b = new Object() {\n        @Override protected void finalize() {\n            System.out.println(this + " was finalized!");\n            finalized = true;\n        }\n        @Override public String toString() {\n            return  "B@"+Integer.toHexString(hashCode());\n        }\n    };\n    @Override protected void finalize() {\n        System.out.println(this + " was finalized!");\n    }\n\n    @Override public String toString() {\n        return super.toString() + " with "+b;\n    }\n\n    public static void main(String[] args) {\n        A a = new A();\n        System.out.println("Created " + a);\n        for(int i = 0; !finalized; i++) {\n            if (i % 1_000_000 == 0)\n                System.gc();\n        }\n        System.out.println("finalized");\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n
class A {\n    static volatile boolean finalized;\n\n    Object b = new Object() {\n        @Override protected void finalize() {\n            System.out.println(this + " was finalized!");\n            finalized = true;\n        }\n        @Override public String toString() {\n            return  "B@"+Integer.toHexString(hashCode());\n        }\n    };\n    @Override protected void finalize() {\n        System.out.println(this + " was finalized!");\n    }\n\n    @Override public String toString() {\n        return super.toString() + " with "+b;\n    }\n\n    public static void main(String[] args) {\n        A a = new A();\n        System.out.println("Created " + a);\n        for(int i = 0; !finalized; i++) {\n            if (i % 1_000_000 == 0)\n                System.gc();\n        }\n        System.out.println("finalized");\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

这表明即使在作用域内具有变量的方法也可以检测到引用对象的终结。此外,从堆变量引用也不一定会阻止垃圾收集,因为该B对象无法访问,因为当包含引用的对象也无法访问时,没有连续的计算可以访问它。

\n\n
\n\n

值得强调的是,即使使用该对象也并不总是阻止其垃圾回收。重要的是正在进行的操作是否需要 object\xe2\x80\x99s 内存,并且并非对源代码中的 object\xe2\x80\x99s 字段的每次访问都必须导致运行时的实际内存访问。规范指出:

\n\n
\n

可以设计程序的优化转换,将可到达的对象数量减少到比天真地认为可到达的对象数量少。[\xe2\x80\xa6]

\n\n

如果对象字段中的值存储在寄存器中,则会出现另一个示例。然后,程序可以访问寄存器而不是对象,并且永远不会再次访问该对象。这意味着该对象是垃圾。

\n
\n\n

这不仅仅是一个理论上的选择。正如在 Java 8 中调用强可达对象的 Finalize()中所讨论的,它甚至可能在对象上调用方法时发生,或者换句话说,this当实例方法仍在执行时,引用可能会被垃圾回收。

\n\n

当然,防止对象垃圾收集的唯一方法是在对象上进行同步(如果终结器也在对象上进行同步或调用)Reference.reachabilityFence(object)Java\xc2\xa09 中添加的方法。栅栏方法的后期添加证明了优化器在不同版本之间不断改进对提前垃圾收集问题的影响。当然,首选的解决方案是编写完全不依赖于垃圾收集时间的代码。

\n

  • @MarquisofLorne 在我的答案的开头是一个链接到[这个答案](/sf/answers/1706615361/),它已经显示了局部变量引用的对象如何被垃圾收集。我只是扩展它来表明这反过来允许堆变量引用的对象的集合。请注意,[JLS §12.6.1](https://docs.oracle.com/javase/specs/jls/se12/html/jls-12.html#jls-12.6.1-410) 明确禁止收集 B在 A 之前;两者都在这里收集在一起,但由于未指定终结顺序,因此 JVM 恰好在 A 之前终结了 B。 (2认同)