我有这样的代码:

public void foo()
{
    Object x = new LongObject();
    doSomething(x);
    //More Code

    // x is never used again
    // x = null helps GB??
    Object x2 = new LongObject();
    doSomething(x2);
}

我希望x分配的内存可以在需要时由GC释放。但是我不知道是否需要将其设置为null还是编译器会这样做。

最佳答案

为了从字面上回答您的问题,编译器(将源代码称为字节码编译器)从不插入null分配,但是通常不必为null分配变量。

正如this answer所解释的,作用域是一个编译时事物,从形式上说,如果对象不能为“ be accessed in any potential continuing computation from any live thread”,则该对象可以进行垃圾回收。但是,对于通过特定实现标识哪个合格对象没有任何保证。正如链接答案也解释的那样,JIT编译代码将仅保留对随后将被访问的对象的引用。这可能会超出您的预期,允许对看起来像在源代码中正在使用的对象进行垃圾回收,因为运行时优化可能会转换代码并减少实际的内存访问。

但是在解释模式下,分析不会走得那么远,并且尽管在此之后甚至在源代码中甚至没有使用该变量,但在当前堆栈框架中可能存在对象引用,从而无法收集引用对象。没有保证在执行该方法时从解释的代码切换到编译的代码能够摆脱这种悬而未决的引用。当实际的大量计算发生在foo()中时,热点优化程序甚至不太可能考虑编译doSomething

不过,这很少是一个问题。运行解释仅在初始化或首次执行期间发生,即使这些对象很大,如果这样的对象比可能的收集时间晚了一点,也几乎没有问题。一个普通的应用程序包含数百万个对象。

但是,如果您认为可能存在问题,则可以轻松解决此问题,而无需将null分配给变量。限制范围:

public void foo()
{
    {
        Object x = new LongObject();
        doSomething(x);
        //More Code
    }
    {
        Object x2 = new LongObject();
        doSomething(x2);
    }
}


除了分配null之外,将变量的范围限制为实际使用可以提高源代码的质量,即使在不影响已编译代码的情况下也是如此。虽然作用域纯粹是源代码,但它可能会对字节码产生影响。在上面的代码中,编译器将重用x在堆栈框架中的位置来存储x2,因此在第二个LongObject执行期间不存在对第一个doSomething的悬挂引用。

如前所述,这对于内存管理来说是很少需要的,提高源代码质量应该驱动您的决策,而不是试图帮助垃圾收集器。

10-04 23:17
查看更多