[英]Garbage Collection in 2.1 Cracking the Coding Interview
对于此代码块(破解编码面试中2.1的解决方案):当您执行prev.next = n.next
,n是否会被垃圾收集器收集? 那你怎么办n = n.next
?
2.1的问题是:编写代码以从未排序的链表中删除重复项。
有人可以在这种情况下向我解释垃圾收集器如何工作吗?
public static void removeDup (LinkedListNode del)
{
LinkedListNode prev = null;
Hashtable myTable = new Hashtable();
while(del!= null)
{
// table does not have the key yet
if (myTable.containsKey(del.data) == false)
{
myTable.put(del.data, true);
prev = del;
}
// table has the duplicate
else
{
prev.next = del.next;
}
del = del.next;
}
}
当您调用prev.next = n.next;
, n
仍可立即用作方法中的变量。 它无处不在,处在生命的黄金时期。 仅当绝对无法访问对象时,对象才会被垃圾回收。
如果要立即跟进,则n = null;
,那么可以肯定,对象n
指向的对象可能会被垃圾回收(假设您没有将其存储在其他任何地方)。 不是马上就引起您的注意,但最终。 关键是,什么时候进行垃圾回收都没有关系,因为您不再有任何方法可以将对该对象的引用返回到您的代码中。
至于垃圾收集器,当你写
del = del.next;
无论先前被删除引用(假设没有其他引用它)有资格被垃圾收集-如果变量del
是引用数据的唯一的事情,一旦del
被重新分配什么会引用了数据,这使得资格GC。 请注意,这并不意味着将立即进行GC处理。
顺便说一句,在您的代码中,您编写的位置
else
{
prev.next = del.next;
}
您将获得NullPointerException
,因为您正试图访问prev的成员,该成员已在上面设置为null。
您重新分配del
在你的方法, del = del.next;
。 因此,如果没有其他直接或间接指向del
引用,则较旧的del
可能有资格进行垃圾回收。
当对象超出范围且没有其他引用指向该对象时,将对其进行垃圾回收。
我觉得,如果我们采用简单的案例并逐步了解正在发生的事情,则更容易理解(以一种简单的方式,实际的实现很可能使用计数器和更复杂的数据结构,但我偏离了方向)。 假设我们有一个链表1 -> 1 -> 2 -> null
传递给此方法,并在进入while循环之前先查看一下堆栈和堆上的内容。第一次:
Heap: {
#1 : { LLN, data: 1 , next #2 },
#2 : { LLN, data: 1 , next #3 },
#3 : { LLN, data: 2 , next: null },
#4 : { HashTable, keyvalues: {} }
}
Stack : {
del: #1
prev: null
myTable: #4
}
从这张图中可以忽略的是,堆栈中用于调用函数的局部变量一直返回到链的顶部,当然还有其他我们不知道的分配对象。
我们也知道一个对象(分配在堆上)在任何代码都无法访问时才有资格进行垃圾回收。 换句话说:如果我不可能从堆栈上的引用开始,然后跟随堆上的引用到达特定位置,则垃圾收集器有资格清除它。
让我们在while循环中进行一次迭代(您应该结束此循环):
Heap: {
#1 : { LLN, data: 1 , next: #2 },
#2 : { LLN, data: 1 , next: #3 },
#3 : { LLN, data: 2 , next: null },
#4 : { HashTable, keyvalues: {1:true} }
}
Stack : {
del: #2
prev: #1
myTable: #4
}
仍然像以前一样,一切都可以实现。 让我们进入第二个迭代,结束else块,然后执行do: prev.next = del.next
。 由于del.next =#3,因此我们为prev.next分配该值并得出以下结论:
Heap: {
#1 : { LLN, data: 1 , next: #3 },
#2 : { LLN, data: 1 , next: #3 },
#3 : { LLN, data: 2 , next: null },
#4 : { HashTable, keyvalues: {1:true} }
}
Stack : {
del: #2
prev: #1
myTable: #4
}
此时,您可以看到堆上的所有内容仍然有效(不符合垃圾回收的条件)。 #1被prev引用,#2被del引用,#4被myTable引用,并且#3到#1和#2都处于活动状态。 现在看看当我执行del = del.next
时会发生什么:
Heap: {
#1 : { LLN, data: 1 , next: #3 },
#2 : { LLN, data: 1 , next: #3 },
#3 : { LLN, data: 2 , next: null },
#4 : { HashTable, keyvalues: {1:true} }
}
Stack : {
del: #3
prev: #1
myTable: #4
}
现在,我堆栈上的所有东西(未知)都没有指向#2,因此此时它可以进行垃圾收集。 正如我之前提到的,我们不了解堆栈的其余部分,因此可能有其他引用它,但是很有可能它已经死了并且可以回收。 正如其他人已经提到的那样,此时不必进行垃圾回收,但是如果确实如此,并且没有其他东西持有对#2的引用,则它可以回收#2。
如果继续进行此练习,您会注意到在while循环的下一次迭代中,#1也将不再被我们从堆栈中了解的任何内容所引用。 但是,调用此方法的方法很有可能具有一个引用#1的局部变量,因此,那时它可能不符合进行垃圾收集的条件。
当然,这是对发生的事情的非常简化的浏览,但这是事物的本质。 如果您有兴趣,还应该研究终结器在实际回收内存时如何发挥作用。 软引用和弱引用也是非常有趣的主题。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.