GC会走多远?

3

Java垃圾回收器能够处理多少数据?假设有以下代码:

public class MyClass {
    private Object myObject = new Object();
    public void clear() {
        myObject = null;
    }
}

public class MyMain {
    ...
    MyClass myClass = new MyClass();
    ...
    myClass.clear();
    myClass = null;
}

我需要在将myClass设置为null之前调用myClass.clear(),以确保GC将删除myClass内的myObject吗?还是仅将myClass设置为null就足够了,GC会删除所有嵌套对象?
也许我有点多虑,答案很简单,即“GC最终将删除任何未被任何正在运行的代码或任何未来可用代码引用的对象”。因此,如果创建的对象在任何运行的代码中都不可用,或永远不会在任何运行的代码中可用,它最终将成为GC的受害者,即使它是嵌套的,这可能会让C++程序员感到恐惧。

3
这些文章关于.NET的垃圾回收器,而不是JVM的,但雷蒙德·陈(Raymond Chen)的博客有几篇好的文章可以帮助你更好地理解这个问题:《每个人都以错误的方式思考垃圾回收》和《对象何时变得可供垃圾回收?》。 - Daniel Pryden
执行 myClass = null; 实际上什么也没做。它只是将指针设置为 null,但对象仍然存在。直到 Java GC 检查该对象是否仍在使用(所有运行线程中是否有对它的引用可用?),然后 GC 将删除它。 - Martijn Courteaux
9个回答

9
不需要调用myClass.clear()。当没有通过实时引用访问对象的方式时,它将有资格进行垃圾回收。这并不意味着它会立即被回收,但它将有资格进行回收。
为了进行垃圾回收而将引用设置为null非常罕见,并且这样做会影响代码的可读性。当然,也有例外情况 - 例如,ArrayList<T>在不再需要元素时会将其内部缓冲区的元素设置为null,以避免意外阻止对象被垃圾回收 - 但通常情况下,只要对象本身还需要,对象中的字段就是有效和有用的。简而言之,我几乎永远不会编写像MyClass.clear()这样的方法,更别说调用它了。
将局部变量设置为null也很少有必要。你的MyMain中的代码不清楚,因为它在方法外无效...如果你的意思是所有的代码都在一个方法中,那么你也不需要将myClass设置为null。当方法结束时,该变量将不再作为GC根引用它所引用的对象 - 当然,如果存在其他GC根,则该对象仍然不会有资格进行垃圾回收。然而,在方法结尾将局部变量设置为null是没有意义的。

如果Java的垃圾回收机制像.NET一样,那么对象比你所暗示的更容易被回收。例如,在实例方法中可能会回收“this”(只要它不再被使用)。 - Craig Gidney
@Strilanc:虽然我不认为这是有保证的,但我在使用的任何JVM中都没有看到过这种行为。我认为我使用的GC甚至会将所有局部变量视为根,直到方法结束,不像.NET GC。 - Jon Skeet

8

为了让对象被GC回收,你不需要将任何东西设置为null。不要过分担心。

不要试图像C++一样编写Java程序。


2
当它超出范围时,它将被垃圾回收。 - Byron Whitlock
10
@Byron:绝对不行。如果你的评论是一个答案,我会给它投反对票。首先,对象不会超出范围 - 变量会。这有很大的区别。其次,一个对象在特定变量超出范围后可能有资格进行垃圾回收,但这并不意味着它会在那时被回收。 - Jon Skeet
2
有时候将变量设置为null是值得的。例如,想象一下如果ArrayList<T>在元素被删除并且剩余元素向下移动后仍然在其后备数组中留下引用 - 这将导致对象从GC的角度可达,即使实际上没有办法访问它们。 - Jon Skeet
@Jon:当然,我不会在这方面与你争论。但是,我认为对于正在学习Java的OP来说,完全合格和正确的答案可能不太清晰。 - Matt Ball

7

通过引用空值来帮助垃圾回收通常是不必要的。

这里有一个人工案例,其中空值确实有所帮助:

    long[] arr = new long[7000000];
    //arr = null;
    arr = new long[7000000];

如果你使用-Xmx100M运行它,会出现OutOfMemoryError错误。如果你取消注释使arr变为null,它将正常工作。这是因为arr引用仍然持有巨大的数组,而另一个数组正在被创建。内存分配发生在赋值之前,它触发了垃圾回收,并失败了。当引用被置为空时,GC成功地释放了足够的空间。


2
不需要先调用clear。垃圾回收器会很高兴地找到未使用的myObject引用并回收它。
实际上,垃圾回收器可以处理相当复杂的对象图,包括循环引用:
public class Foo
{
    public HashMap a;
    public HashMap b;

    public Foo() {
        a = new HashMap();
        b = new HashMap();
        a.put("b", b);
        b.put("a", a);
    }
}
// ...
public void someFunction() {
   Foo f = new Foo();
}

我通常不会将ab设置为公共的,但我在这里试图达到极端的效果。

您不需要清除fab。如果没有任何东西引用f,最终会检查f是否可达,GC不会因为ab相互引用而出现问题。

详细信息(非常详细)请参见JLS


1

是的,你过于谨慎了。只要一个对象不被运行代码所访问,它最终会被收集(尽管“最终”可能意味着“当程序退出”,如果有足够的内存且垃圾回收器不需要运行)。不可访问对象内部的引用也不再被认为是可达的,因此对于垃圾回收器来说,它们不再被计算。

请注意,这意味着 finalize 可能已经在你的子对象中运行过,因此如果您想在终结器中执行任何工作,则不能依赖任何非原始类型的子类。


子对象可能会被终结,但它们肯定不会被收集!那会引入巨大的漏洞。 - Craig Gidney
漏洞已经存在。不能信任已被终结的任意对象的任何属性,因为终结器中的显式假设是除非你使对象“不是垃圾”,否则该对象将永远不会再次使用。既然如此,从“已经终结”到“已经收集”的跨度就变得非常小了。事实上,在漏洞方面,立即丢弃对象是更安全的做法--虚拟机将明确地在一个不存在的对象上操作(它可能像 null 一样运行,也可能抛出异常),而不是在一个现有但可能不一致的对象上操作。 - cHao

0

垃圾回收器最终会正确地处理对象。我认为你可能有点过于多虑了。虽然将对象设置为null并不会有害。

回答你的问题,如果你已经调用了myObject.clear,它只是将myObject设置为null,那么没有必要再次将其重置为null。


0

你甚至不需要将myclass设置为null。一旦它超出作用域,它就可以进行垃圾回收。


0

垃圾回收器将处理所有没有引用的对象。因此,在MyClass被处理后,就不会再有对myObject的引用,因此它将被垃圾回收器移除。


0
我需要在将myClass设置为null之前调用myClass.clear()来确保GC会删除myClass内部的myObject吗?
您不必调用clear,也不必将myClass设置为null。
您也无法确保它会被回收,但是如果VM需要内存,则当myClass本身不可引用时,它将被标记为可回收对象。
因此,在这种情况下,您不必担心。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接