Memory management 将指针设置为nil以防止Golang中的内存泄漏

Memory management 将指针设置为nil以防止Golang中的内存泄漏,memory-management,go,memory-leaks,garbage-collection,memory-profiling,Memory Management,Go,Memory Leaks,Garbage Collection,Memory Profiling,我正在学习围棋,作为练习,我想实现一个链表。作为参考,我查看了官方的Go代码()。有一件事让我难以忘怀,那就是这些台词: 108 // remove removes e from its list, decrements l.len, and returns e. 109 func (l *List) remove(e *Element) *Element { 110 e.prev.next = e.next 111 e.next.prev = e

我正在学习围棋,作为练习,我想实现一个链表。作为参考,我查看了官方的Go代码()。有一件事让我难以忘怀,那就是这些台词:

   108  // remove removes e from its list, decrements l.len, and returns e.
   109  func (l *List) remove(e *Element) *Element {
   110      e.prev.next = e.next
   111      e.next.prev = e.prev
   112      e.next = nil // avoid memory leaks
   113      e.prev = nil // avoid memory leaks
   114      e.list = nil
   115      l.len--
   116      return e
   117  } 
我很好奇,在这种情况下,如何将指针设置为nil来防止内存泄漏?如果可能的话,我想构造一个有此缺陷的程序,并在使用pprof进行评测时看到它(我将使用修改后的list.go版本,而不使用此nil指针设置)


为清楚起见:如果其中一个节点具有指向它的外部指针,则所有相邻的已删除节点都将具有通过该指针的活动引用,并且不会被删除。

  • 我们创建一个指向Node2的外部指针
  • 我们从列表中删除节点2-4
  • 在这一点上,您只希望节点1,2& 5个是活的,其余的是GC-ed。但是,由于节点2仍然存在 指向节点3等,整个链保持未收集状态

  • Golang垃圾收集器基于三色标记和扫描算法。 简而言之,程序使用的每个内存都与一种颜色相关联。颜色决定了内存是否应该被丢弃

    如果某个内存未被引用(直接或间接),此算法将标记要释放的内存。但如果我们看一下代码:

    e.prev.next = e.next
    e.next.prev = e.prev
    
    这会将e.next中的指针复制到e.prev.next中。现在,假设您想通过一个新的完全创建的元素更新e.prev.next

    之前删除的元素不会被丢弃,因为它仍然被e.next引用

    这就是为什么会有这些行:

    e.next = nil // avoid memory leaks
    e.prev = nil // avoid memory leaks
    

    这可以防止留下旧引用,从而防止内存泄漏。

    您的假设是正确的。如果有一组指针相互指向,但没有指向该组任何成员的引用/指针,则垃圾收集器将检测到该组不可访问,并将正确释放该组

    但对内存泄漏的解释很简单。我们可以从列表中获取包装器,其中包含未报告的
    元素.next
    元素.prev
    指向列表中下一个和上一个元素的指针

    如果这些指针未设置为
    nil
    ,则从列表中删除元素时,它们将保留对下一个和上一个元素包装器的引用,包括与这些元素关联的值

    请参见此示例:

    var e2 *list.Element
    
    func main() {
        listTest()
        fmt.Println(e2.Value)
        // At this point we expect everything from the list to be
        // garbage collected at any time, we only have reference to e2.
        // If e2.prev and e2.next would not be set to nil,
        // e1 and e3 could not be freed!
    }
    
    func listTest() {
        l := list.New()
        e1 := l.PushBack(1)
        e2 = l.PushBack(2)
        e3 := l.PushBack(3)
        // List is now [1, 2, 3]
        fmt.Println(e1.Value, e2.Value, e3.Value)
        l.Remove(e2)
        // Now list is [1, 3], it does not contain e2
    }
    
    listest()
    中,我们构建了一个包含3个元素的列表,并将第2个元素存储在一个全局变量
    e2
    中。然后我们移除这个元素。现在我们希望,除了
    e2
    (以及包装在其中的值)之外,当
    listTest()
    返回时,所有其他内容都会被垃圾收集,因为在
    listTest()
    函数之外无法访问该列表。是的,我们在
    e2
    中有一个指向元素的指针,但是
    e2
    已经(应该)与列表没有任何关系,因为我们删除了它


    如果
    e2
    中的
    prev
    next
    指针未设置为
    nil
    ,则它们所指向的元素中包装的值将永远无法递归释放。但是由于正确地将它们设置为
    nil
    ,在上面的示例中,
    e1
    e3
    ——以及包装在其中的值——将被释放(在下一次垃圾收集运行时)。

    我在上面添加了一条注释,解释了我认为会发生什么。我不确定你所说的“删除的元素仍然被e.next引用”是什么意思,e不是删除的元素吗?我用你描述的图片回答了这个问题,如果我错了,请纠正我。实际上,我用一个修改过的(带有内存泄漏错误)版本的列表来尝试这个方法,我可以看到它没有释放内存。