我的Java老师(高中课程)正在谈论循环,她说如果你有一个for
循环:
for (int i = 0; i < max; i++) {
//something
}
你不能在循环之外使用变量i
,因为垃圾收集功能会删除它,因为它感觉到它是“不需要的”(我知道范围并且这是BS,因为同样的事情发生在所有语言和C ++甚至没有垃圾收集)。现在的问题是......垃圾收集实际上做了什么? (我查了一下,它与堆有关,我还不知道,所以有人向我解释这个)
由于
答案 0 :(得分:7)
(我知道范围,这是BS,因为所有语言都会发生同样的事情,C ++甚至没有垃圾收集)。
正确。由于范围的原因,变量 i
不能在循环外使用 - 它与GC没有没有(在潜在的对象可达性之外) )。
现在问题是......垃圾收集实际上做了什么? (我查了一下,它与堆有关,我还不知道,所以有人向我解释这个)
垃圾收集器负责回收不再是strongly-reachable 的对象。垃圾收集器 nothing [直接]与变量有关,尽管变量可以使对象保持强可达性。 (此外,原始值,例如int
不是对象因此从不由GC处理; - )
我建议您阅读Chapter 9 of Inside the Java Virtual Machine: Garbage Collection和The Truth About Garbage Collection,因为我相信他们会提供足够的答案/见解和理由。 (垃圾收集wikipedia entry也是一个良好的开端,很好地总结了GC。)
来自“真相”:
对象在没有更多强引用存在的情况下进入无法访问状态[不强烈可达]。 当一个对象无法访问时,它是收集的候选者。注意措辞:仅仅因为一个对象是收集的候选者并不意味着它会被立即收集。 JVM可以自由地延迟收集,直到对象立即需要占用内存为止。
快乐的编码。
答案 1 :(得分:4)
你老师的例子不是很好,因为i
可能存储在堆栈上,因为它是一个原语。一个更好的例子是:
public String helloWorld() {
StringBuilder builder = new StringBuilder();
builder.append("Hello");
builder.append(" ");
builder.append("World!");
return builder.toString();
}
在函数的第一行,我们分配一个新对象(new StringBuilder()
)。这个allocates some memory in the heap,后来需要被释放。在C ++中,你最后会做delete builder
来处理它(或者在堆栈上分配它 - 但你不能在Java中这样做,所以我认为这是一个合理的例子。)
垃圾收集是一种替代方法,在函数结束时builder
没有任何反应。相反,定期地,一个称为垃圾收集器的进程运行,并检查正在使用或未使用的对象,并删除任何不使用的对象。在我给出的示例中,垃圾收集器将运行,注意到无法再访问builder
并删除它。
Java的默认垃圾收集器执行称为“标记和扫描”的操作,它基本上遍历它可以访问的所有变量并标记它们(设置了一些标志)。然后删除任何未标记的内容。
我认为在更低的层次上,它实际上做的是移动新内存位置可访问的所有内容,并删除旧内存位置中的任何内容(因为任何内容仍无法访问)。
更简单的垃圾收集方法称为“引用计数”,其中动态分配的所有内容都具有引用计数 - 告诉程序有多少变量指向该内存位置。如果引用计数达到0,则没有人使用该内存,可以立即释放它。上次我检查时,标准Python解释器(CPython)使用它。
引用计数的问题是你可以得到循环:
class Node {
Node next;
}
public void breakReferenceCountingAlgorithm() {
Node a = new Node();
Node b = new Node();
a.next = b;
b.next = a;
}
在此函数结束时,a和b都被引用一次(彼此相同),但它们不可访问。无论如何,Java都会捕获它并且垃圾收集它们。 Python不会。
另一方面,你不能在你给出的那个循环之外使用i
的原因是作用域,而不是垃圾收集。在函数内部,i
的内存可能仍然可用,编译器不允许您访问它。主要是这样你可以这样做:
for(int i = 0; i < 100; i++) {
System.out.println("stuff");
}
// This i is a different variable
for(int i = 0; i < 100; i++) {
System.out.println("more stuff");
}