41

myLocalVar = null;在离开方法之前,我被迫将语句添加到 finally 子句中。原因是为了帮助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
  }
}
4

15 回答 15

32

有一篇旧的 Sun 文档,Java Platform Performance(遗憾的是,链接现在已损坏,我无法找到新的),其中描述了将超出范围的局部变量归零实际上会产生影响的情况在 GC 上。

但是,该论文提到了一个非常旧的 java 版本。正如这个问题中提到的(其中还包含论文中描述的问题的概要),这不再影响当前的 JVM 实现。

于 2009-02-02T16:00:00.377 回答
18

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

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

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

最后,有些人过度依赖 GC 并认为它会为他们执行功能关闭(例如,当变量被删除时关闭连接)当然不是这样。我认为这条线的来源是“我真的已经完成了,但我不确定如何确保这一点”。

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

于 2009-01-23T17:08:22.943 回答
9

在这种情况下不是。myLocalVar 在函数返回后立即超出范围,因此将引用设置为 null 绝对没有任何作用。

于 2009-01-23T17:06:39.357 回答
8

这是一个神话,可以追溯到 java 刚出现时 C++ 的人不信任 gc。

gc 知道它在做什么。清空 var 不会伤害任何东西,但它也不会真正帮助任何东西。就在前几天,杰夫对此发表了一篇非常有趣的帖子

于 2009-01-23T17:05:31.607 回答
7

据我所知,null在变量离开作用域之前立即对其进行 ing 对垃圾收集器没有任何影响。

当然,在某些情况下它确实有帮助。例如 whenvar不是局部变量而是成员或静态成员。然后销毁引用可能会使对象无法访问,从而有资格被收集。

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

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();
}
于 2009-01-23T17:10:24.763 回答
5

在某些极端情况下,将局部变量归零确实可以提供帮助。这不适用于原始问题中的情况,但无论如何都是有教育意义的......让我们考虑这个程序:

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

           // inner = null;
       }

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

如果inner = null;被注释掉,则local变量中的对象在 while 循环期间不能被垃圾收集。原因是 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 对 java-cz 邮件列表的评论的启发(用捷克语,抱歉)

于 2009-02-24T20:16:20.520 回答
3

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

您还可以在 InformIT的文章创建和销毁 Java 对象中看到这一点。阅读整篇文章以找到 Joshua Bloch 同意你的地方。

当局部变量超出范围时,它与将对它的引用设为空是完全相同的。

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

于 2009-01-23T18:00:19.233 回答
2

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

回到 JDK1.3,我确实有一个非常大的对象图的案例,它还包含图中的大量循环引用。清除一些引用确实显着改善了 GC 时间。

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

于 2009-01-23T17:07:25.940 回答
1

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

有人可能在某个时候得到了该建议,并将其误解为需要始终清除局部变量。

于 2009-01-23T18:49:42.723 回答
1

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

  • 在一个字段中创建一个大对象的单元测试中。单元测试人员可能会在所有测试的生命周期内保留测试对象和您创建的对象。这可能会导致测试仪内存不足。在这种情况下,通常最好使用局部变量而不是字段,但是如果需要字段,可以在 tearDown 中将其清除。
  • 循环引用可以由 GC 清理,但不能使用简单的增量收集。这可能意味着具有循环引用的对象需要更多的工作才能清除,并且可以比其他情况下存活得更久。通常这无关紧要,但如果你想减少你的完整 GC 时间,它可以帮助打破循环引用。
于 2009-01-23T21:19:56.290 回答
0

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

简短的回答虽然没有必要,但标记和扫描最终会得到它。顶多是时间问题。

于 2009-01-23T17:12:14.150 回答
0

在某些情况下,空变量(通常是实例或类变量)可能很有用。但是在方法结束之前立即清空局部变量绝对没有任何作用。

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

于 2009-01-23T17:14:33.337 回答
0

如果您的班级长时间闲逛,则将其引用的对象清空将允许收集它们。

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

当您想到对象分配和释放时,请注意“系统”处理的事情:活动线程、尚未被 dispose()d 处理的窗口以及其他一两件事,但我记不清了现在。

系统中的每个对象都“挂”在一个巨大的倒挂树中的这些挂载点上。如果你从这些“根”上剪下任何树枝,整个树枝都会掉到地上,并由垃圾收集器的割草机收集。

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

(顺便说一下,这些修剪非常有效,甚至超过了 C++ 的免费,因为它们不需要在每个对象被释放时触摸它)

于 2009-01-23T17:32:14.297 回答
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
}

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

于 2009-04-04T02:39:20.403 回答
0

将这样的局部变量置空不仅对 GC 而言毫无意义,而且可能会导致将变量不必要地加载到寄存器中以将其置空,从而使情况变得更糟。想象一下,如果在最后一次读取或写入 myLocalVar 之间有 1000 行代码,然后您引用它只是为了使引用无效。该值早已从寄存器中消失,但您必须将其重新加载到内存中才能使用它。

于 2009-04-06T14:43:42.453 回答