它是否有助于GC在Java中使局部variables为空?
我'被迫'添加myLocalVar = null;
声明成为离开方法之前的finally子句。 原因是帮助GC。 有人告诉我,在下次服务器崩溃的时候,我会收到短信,所以我最好这样做:-)。
我认为这是毫无意义的,因为myLocalVar被限制在方法中,只要方法退出,将会“丢失”。 额外的空值只会污染代码,否则是无害的。
我的问题是,这个关于帮助GC从何而来的神话在哪里? (我被称为“Java记忆书”)你知道任何有关“当局”的文章,更深入地解释它吗? 有没有可能这不是一个神话,但真的有帮助吗? 如果是这样,怎么样? 可以使局部variables无效造成任何危害?
澄清,方法是这样的:
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 } }
有一个旧的Sun文档, Java平台性能 (现在损坏的链接现在已经坏了,我还没有find一个新的),它描述了一个情况,即将一个局部variables放在范围之外实际上是有效的在GC上。
不过,这篇论文提到了Java的一个非常老的版本。 正如这个问题 (它也包含了本文所描述的问题的一个简介)中所提到的,这不再影响当前的JVM实现。
Java GC应该是“健全的”,但不一定立即“完成”。 换句话说,它的devise使得它永远不会消除仍然可以被至less一条path访问的对象(从而导致一个悬而未决的引用)。 它不一定立即完成,因为它可能需要时间,直到它消除所有可以删除的东西。
我认为,大多数GC的神话来自对这个概念的误解。 许多人将太多的实例variables保留下来,导致问题,但这当然不是问题。
其他人把局部variables放在一个实例variables(例如,通过传递给函数),然后认为取消局部variables以某种方式消除了variables,这当然是不真实的。
最后,还有一些人过分关注GC,并认为它会对它们进行functionclosures(例如,当variables被移除时closures连接)当然不是这种情况。 我认为这条线的来源是“我真的做了,但我不知道如何确保”。
所以是的,你是对的,这是不必要的。
不是在这种情况下。 只要函数返回,myLocalVar就会超出范围,因此将引用设置为null是毫无用处的。
这是一个神话,回到当java第一次出来,C ++的人不相信gc。
gc知道它在做什么。 清空var不会伤害任何东西,但它不会真的帮助任何事情。 杰夫前些日子有一个很有趣的post 。
据我所知,在离开作用域之前立即清空variables对垃圾收集器没有任何影响。
当然有些情况确实有帮助。 例如,当var
不是一个本地variables,而是一个成员或静态成员。 然后销毁引用可能会使对象无法访问,从而有资格收集。
另一种情况是,如果一个函数分配了很多的临时内存来初始化一些数据以便进一步处理,并且可以在开始处理之前丢弃对临时内存的所有引用,那么它甚至可以帮助使用局部variables:
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(); }
你是对的。 排除一个会立即超出范围的variables是不必要的,并且与GC无关。 它所做的只是混淆了代码。 在Effective Java 2nd Edition中 ,作者build议不要在本地variables中不必要的空值。 请参阅Effective Java,第2版,第6项:消除过时的对象引用,以进行全面的写作。
您也可以在InformIT的文章“ 创build和销毁Java对象”中看到这一点。 阅读整篇文章 ,findJoshua Bloch与你同意的地方。
当一个局部variables超出范围时,就像您将其引用归零一样。
编辑:在Sun网站添加链接到有效的Java第二版
零局部variables确实可以帮助一些边缘情况。 这不适用于原始问题的情况,反正是教育……让我们考虑这个程序:
public class Main { public static void main(String[] args) { { Main local = new Main(); // inner = null; } while (true) { // long running method } } }
如果inner = null;
被注释掉了,在while循环期间, local
variables中的对象不能被垃圾收集。 原因是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 */ }
没有关于局部variables范围的信息。 所以从JVM的angular度来说,上面的程序相当于:
public class Main { public Main() { } public static void main(String args[]) { Main main1 = new Main(); do ; while(true); } }
(由JAD反编译器生成)
结论:在这种非常特殊的情况下,有一些原因使局部variables无效。 但是,如果方法即将完成(就像我原来的问题),这并没有帮助。
这受到了Zdenek Tronicek在java-cz邮件列表 (捷克语,对不起)的评论,
正如你正确地指出的那样,在这种情况下归零是毫无意义的。
回到JDK1.3,我确实有一个非常大的对象图的情况,在图中也包含了大量的循环引用。 清除一些参考文件确实可以明显改善GC时间。
我不确定这是否适用于现代虚拟机。 垃圾收集者变得越来越聪明。
在这个神话中另一个可能的因素是,如果你在方法结束之前完成了一个局部variables的话,它可以有所作为。 这将允许GC在方法完成之前收集该对象,这可能是有用的。
有人可能会在某个时候给出这个build议,并误解为需要总是将局部variables清空。
只有两种情况,我发现将variables设置为null是有用的:
- 在unit testing中,在一个字段中创build一个大对象。 unit testing人员可以保留testing对象和您为所有testing生命创build的对象。 这可能会导致testing仪内存不足。 在这种情况下,最好使用局部variables而不是字段,但是如果需要的字段可以在tearDown中清除。
- 循环引用可以由GC清理,但不能用简单的增量收集。 这可能意味着带有循环引用的对象需要更多的工作来清除,并且可以比其他方式活得更久。 一般来说,这并不重要,但如果您尝试缩短完整的GC时间,则可以帮助打破循环引用。
我不知道技术细节,但据我记忆,variables只不过是从当前栈帧引用,直到这个引用被删除,对象不能被垃圾收集。 现在,但是明确地把它设置为null,你已经确定了引用没有了。 如果你不这样做,你基本上是让虚拟机决定什么时候这个引用被清除了,这可能是也可能不是在退出这个范围的时候(不同于C ++,如果这个对象位于堆栈上并且必须被销毁)。 这可能是堆栈帧被下一个覆盖的时候。 我不确定是否真的有这样的虚拟机。
简单的答案虽然,没有必要,标记和扫描将最终得到它。 这至多是一个时间问题。
在某些情况下,对variables(通常为实例或类variables)进行空值是很有用的。 但是在方法结束之前立即删除一个局部variables是完全没有用的。
当您将一个variables设置为null
,您只是将该引用移除到实际对象。 但是当一个局部variables超出范围时,参考被移除; 因此,将其设置为空,因为方法的最后一行很简单。
如果你的class级长时间挂着,那么将它引用的对象归零将允许它们被收集。
这几乎从来都不是问题,大多数时候将对象归零是没有用的。
当你想到对象分配和释放的时候,要注意“系统”所处理的东西:活动线程,还没有被处理的窗口()d,还有一两件事情,但是我不记得现在。
系统中的每个对象都在一个巨大的倒置树中“挂起”这些挂载点。 如果从这些“根”上切下任何分支,整个分支落到地上,并由割草机收集垃圾。
大多数类在整个生命周期中都需要所有的成员variables – 当他们的生命结束时,他们的整个分支都被剪掉,包括所有的成员; 因此 – 不需要null。
(顺便说一句,这些修剪是非常有效率的,甚至比C ++的自由度还要高,因为它们在释放时不需要触摸每个对象)
如果您不再需要本地作用域中的大对象,则可以给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自动完成的。 所以我同意之前的所有贴子。
不仅在GC方面把一个局部variables置零是没有意义的,它可能会导致不必要的variables加载到寄存器中,以便将其清零,这使情况变得更糟。 想象一下,如果在上次读取或写入myLocalVar之间是否有1000行代码,然后您引用它只是为了清空引用。 该值已经从registry中消失了,但您必须将其重新加载到内存中才能使用。