垃圾收集2.1破解编码访谈

时间:2014-01-04 00:36:37

标签: java garbage-collection linked-list

对于这段代码(破解编码面试时2.1的解决方案):当你执行prev.next = n.next时,是否会被垃圾收集器收集?那你怎么做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;
    }
}

4 个答案:

答案 0 :(得分:0)

当您致电prev.next = n.next;时,n仍然立即可用作为您方法中的变量。它不会去任何地方,它正处于生命的黄金时期。 无论如何,只有在你无法访问它们时,对象才会收集垃圾。

如果您要立即使用n = null;跟进,那么确定,指向的对象n可能会被垃圾收集(假设您没有将其存储在其他地方)。不是在想你,而是最终。关键是,垃圾收集何时发生并不重要,因为您不再有任何方法可以将该对象的引用返回到您的代码中。

答案 1 :(得分:0)

关于垃圾收集器,当你写

 del = del.next;

以前引用的任何内容(假设没有其他内容引用它)符合条件的是垃圾收集的 - 如果变量del是引用该数据的唯一内容,一次{ {1}}被重新分配,不再引用该数据,这使得它符合GC的条件。请注意,这并不意味着它会立即成为GC。

BTW,在你的代码中,你写的地方

del

您将获得else { prev.next = del.next; } ,因为您正在尝试访问prev的成员,该成员已在上面设置为null。

答案 2 :(得分:0)

您将del重新分配给方法del = del.next;。因此,如果没有其他引用直接或间接指向del,那么较旧的del可能有资格进行垃圾回收。

当对象超出范围且没有其他引用指向该对象时,对象将被垃圾收集。

答案 3 :(得分:0)

我觉得,如果我们采用一个简单的案例,并且了解正在发生的事情(以一种简单的方式,实际的实现可能使用计数器,以及更复杂的数据结构,但我离题),这更容易理解。假设我们有一个传递给这个方法的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的局部变量,因此它可能不适合进行垃圾收集。

这当然是对正在发生的事情进行非常简化的过程,但它是事物的主宰。如果您有兴趣,还应该了解终结器在实际回收内存时的作用。软弱参考也是非常有趣的话题。