它是否有助于GC在Java中使null变为null

时间:2009-01-23 17:01:25

标签: java variables garbage-collection null local

在离开方法之前,我被'强制'在finally子句中添加myLocalVar = null;语句。原因是帮助GC。有人告诉我,下次服务器崩溃时我会收到短信,所以我最好这样做: - )。

我认为这是毫无意义的,因为myLocalVar的范围是方法,并且一旦方法退出就会“丢失”。额外的归零只会污染代码,否则无害。

我的问题是,这个关于帮助GC的神话来自哪里? (我被称为“Java记忆书”)你知道“权威”的任何文章更深入地解释它吗?有可能这不是一个神话,但真的有所帮助吗?如果是这样,怎么样?可能会使局部变量归零会造成任何伤害吗?

澄清一下,方法如下:

void method() {
  MyClass myLocalVar = null;
  try {
    myLocalVar = get reference to object;
    ... do more here ...
  } finally {
    if (myLocalVar != null) {
      myLocalVar.close(); // it is resource which we should close
    }

    myLocalVar = null; // THIS IS THE LINE I AM TALKING ABOUT
  }
}

15 个答案:

答案 0 :(得分:28)

有一段旧的Sun文档, Java Platform Performance (遗憾的是现在已断开了,我找不到新的文档),它描述了一个归零的情况退出范围的局部变量实际上对GC有影响。

然而,该论文提到了一个非常古老的java版本。如this question中所述(其中还包含本文中描述的问题的原理),这不再影响当前的JVM实现。

答案 1 :(得分:19)

Java GC应该是“声音”但不一定立即“完整”。换句话说,它被设计成永远不会消除至少一条路径仍可访问的对象(从而导致悬空引用)。它不一定立即完成,因为它可能需要一些时间才能删除所有可以删除的内容。

我认为大多数GC神话来自对这个概念的误解。 许多人保留了太多实例变量,这会导致问题,但这当然不是问题。

其他人将局部变量放在一个实例变量中(例如,通过将其传递给函数),然后认为使局部变量无效以某种方式消除变量,这当然是不真实的。

最后,有些人过分关注GC,并认为它会对它们进行功能关闭(例如,当删除变量时关闭连接),当然不是这种情况。我认为这一行的来源是“我真的已经完成了它,但我不确定如何确保”。

所以是的,你说这是不必要的。

答案 2 :(得分:9)

不是这种情况。一旦函数返回,myLocalVar就会超出范围,因此将引用设置为null绝对不会。

答案 3 :(得分:8)

这是一个神话,可以追溯到java首次出现时,C ++家伙不信任gc。

gc知道它在做什么。归零var不会伤害任何东西,但它也不会真正帮助任何东西。杰夫前几天有pretty funny post

答案 4 :(得分:5)

据我所知,null在变量离开作用域之前立即对变量进行处理没有任何影响。

当然 的情况确实有帮助。例如。当var不是局部变量而是成员或静态成员时。然后销毁引用可能会使对象无法访问,因此有资格进行收集。

如果函数分配了很多临时内存来初始化一些数据以进行进一步处理,并且在开始处理之前可以丢弃对临时内存的所有引用,那么它甚至可能对局部变量有帮助的情况:

SomeResult SomeFunction(SomeClass param) {
    TempData big = new TempData(param);
    IntermediateResult intermediate = big.GetIntermediateResult();
    big = null; // allow GC to reclaim the memory before returning from the function
    intermediate.FurtherProcessing();
    return intermediate.EvenMoreProcessing();
}

答案 5 :(得分:3)

你是对的。无论如何,取消一个将立即超出范围的变量是不必要的,并且对GC没有任何影响。所有这一切都使代码混乱。在Effective Java 2nd Edition中,作者建议不要使用局部变量进行不必要的归零。有关完整的文章,请参阅Effective Java,第2版,第6项:消除过时的对象引用。

您也可以在InformIT的文章Creating and Destroying Java Objects中看到这一点。阅读整篇文章,找到Joshua Bloch同意你的地方。

当一个局部变量超出范围时,它与你对它的引用为空的方式完全相同。

编辑:在Sun网站上添加指向Effective Java 2nd Edition的链接

答案 6 :(得分:3)

在一些边缘情况下,将局部变量确实有用。这不适用于原始问题的情况,但无论如何都是教育......让我们考虑这个程序:

public class Main {
    public static void main(String[] args) {
       {
           Main local = new Main();

           // inner = null;
       }

       while (true) {
           // long running method
       }
    }
}

如果inner = null;被注释掉,则{while}循环中的local变量中的对象不能被垃圾收集。原因是Java虚拟机不知道这样的范围。它只是:

D:\workspaces\workspace-3.4\test\src>javap -verbose -c Main
public class Main extends java.lang.Object
  minor version: 0
  major version: 50
  Constant pool:
const #1 = Method       #4.#11; //  java/lang/Object."<init>":()V
const #2 = class        #12;    //  Main
const #3 = Method       #2.#11; //  Main."<init>":()V
const #4 = class        #13;    //  java/lang/Object
const #5 = Asciz        <init>;
const #6 = Asciz        ()V;
const #7 = Asciz        Code;
const #8 = Asciz        main;
const #9 = Asciz        ([Ljava/lang/String;)V;
const #10 = Asciz       StackMapTable;
const #11 = NameAndType #5:#6;//  "<init>":()V
const #12 = Asciz       Main;
const #13 = Asciz       java/lang/Object;

{
public Main();
  Code:
   Stack=1, Locals=1, Args_size=1
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return

public static void main(java.lang.String[]);
  Code:
   Stack=2, Locals=2, Args_size=1
   0:   new     #2; //class Main
   3:   dup
   4:   invokespecial   #3; //Method "<init>":()V
   7:   astore_1
   8:   goto    8
  StackMapTable: number_of_entries = 1
   frame_type = 8 /* same */

}

没有关于局部变量范围的信息。所以从JVM的角度来看,上面的程序相当于:

public class Main
{

    public Main() { }

    public static void main(String args[])
    {
        Main main1 = new Main();
        do
            ;
        while(true);
    }
}

(由JAD反编译器生成)

结论:在这种非常特殊的情况下,有一些理由可以归零局部变量。但是如果方法即将完成(就像在我原来的问题中那样),那就无济于事了。

这受到了来自Zdenek Tronicek on java-cz mailing list的评论的启发(用捷克语,抱歉)

答案 7 :(得分:2)

正如你正确指出的那样,在这种情况下归零是完全没有意义的。

回到JDK1.3我实际上有一个包含极大对象图的案例,该图还在图中包含了许多循环引用。清除一些参考文献确实可以明显改善GC时间。

我不确定这是否适用于现代VM。垃圾收集器变得越来越聪明。

答案 8 :(得分:1)

这个神话中另一个可能的因素是,如果你在方法结束之前 之前完成了局部变量,那么它可以产生影响。这将允许GC在方法完成之前收集该对象,这可能很有用。

有人可能会在某个时候给出建议并误解为需要始终将局部变量置空。

答案 9 :(得分:1)

只有两种情况我发现将变量设置为null非常有用:

  • 在单元测试中,在字段中创建大对象。单元测试器可以保留测试对象和您在所有测试的生命周期中创建的对象。这可能导致测试仪内存不足。在这种情况下,通常最好使用局部变量而不是字段,但如果需要字段,则可以在tearDown中清除它。
  • 循环引用可以由GC清除,但不能通过简单的增量收集清除。这可能意味着具有循环引用的对象需要更多的工作来清除,并且可以比其他方式长寿。一般来说这没关系,但如果你想减少完整的GC时间,它可以帮助打破循环引用。

答案 10 :(得分:0)

我不知道技术细节,但据我记忆,变量只不过是来自当前堆栈框架的引用,并且在删除此引用之前,该对象不能被垃圾回收。现在,但明确地将其设置为null,您已确保引用已消失。如果你不这样做,你基本上就让VM决定何时清除这个引用,退出范围时可能会或者可能不会(与C ++不同,如果对象位于堆栈上并且必须被销毁)。可能是堆栈帧被下一个覆盖。我不确定是否真的有这样做的VM。

简短的回答,这是不必要的,标记和扫描最终会得到它。这至多是一个时间问题。

答案 11 :(得分:0)

在某些情况下,null变量(通常是实例或类变量)可能很有用。但是在方法结束之前立即将 local 变量置零无效。

当您将变量设置为null时,您只是删除对实际对象的引用。但是当局部变量超出范围时,无论如何都会删除引用;因此,将其设置为null,因为方法的最后一行只是多余的。

答案 12 :(得分:0)

如果你的班级长时间闲逛,那么将它引用的对象归零将允许它们被收集。

这几乎不是问题,大多数时候归零对象都没用。

当你想到对象分配和释放时,要注意“系统”处理的东西:活动线程,没有被处置的窗口()d,还有一两件事,但我可以'记得现在。

系统中的每个对象都会在一个巨大的倒置树中“挂起”这些挂载点。如果你从这些“根”中切掉任何分支,那么整个分支就会掉到地上并被草坪割草机收集。

大多数课程在整个生命周期中都需要所有成员变量 - 当他们的生命结束时,他们的整个分支都会被修剪,包括所有成员;因此 - 不需要为空。

(顺便说一句,这些修剪非常有效,甚至比C ++更自由,因为它们不需要触及每个对象,因为它被释放了)

答案 13 :(得分:0)

如果您不再需要本地范围中的大对象,则可以为JVM提供提示并将引用设置为NULL。

public void foobar()
{
    List<SomeObject> dataList = new ArrayList<SomeObject>();

    // heavy computation here where objects are added to dataList 
    // and the list grows, maybe you will sort the list and
    // you just need the first element... 

    SomeObject smallest = dataList.get(0);

    // more heavy computation will follow, where dataList is never used again
    // so give the JVM a hint to drop it on its on discretion
    dataList = null;

    // ok, do your stuff other heavy stuff here... maybe you even need the 
    // memory that was occupied by dataList before... 

    // end of game and the JVM will take care of everything, no hints needed
}

但是在返回之前它没有意义,因为这是由JVM自动完成的。所以我同意以前的所有帖子。

答案 14 :(得分:0)

在GC方面,不仅将局部变量归零,因为它无意义地将变量加载到寄存器中以使其无效,这使情况变得更糟。想象一下,如果最后一次读取或写入myLocalVar之间有1000行代码,那么引用它只是为了使引用无效。该值已从寄存器中消失,但您必须将其重新加载到内存中才能使用它。