在离开方法之前,我被'强制'在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
}
}
答案 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非常有用:
答案 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行代码,那么引用它只是为了使引用无效。该值已从寄存器中消失,但您必须将其重新加载到内存中才能使用它。