draveness / blog-comments

面向信仰编程
https://draveness.me
140 stars 6 forks source link

Go 语言垃圾收集器的实现原理 | Go 语言设计与实现 · /golang-garbage-collector #191

Closed draveness closed 2 years ago

draveness commented 4 years ago

https://draveness.me/golang-garbage-collector/

Go 语言垃圾收集器的实现原理

15389033811 commented 4 years ago

您好,图7-31那里说,增量式会增加一次gc循环时间,请问这句话怎么理解呢

draveness commented 4 years ago

您好,图7-31那里说,增量式会增加一次gc循环时间,请问这句话怎么理解呢

因为增量式的垃圾回收一定会引入额外开销(写屏障),所以一次 GC 的总时间会大于 STW GC 的时间

15389033811 commented 4 years ago

@draveness

您好,图7-31那里说,增量式会增加一次gc循环时间,请问这句话怎么理解呢

因为增量式的垃圾回收一定会引入额外开销(写屏障),所以一次 GC 的总时间会大于 STW GC 的时间

@draveness

您好,图7-31那里说,增量式会增加一次gc循环时间,请问这句话怎么理解呢

因为增量式的垃圾回收一定会引入额外开销(写屏障),所以一次 GC 的总时间会大于 STW GC 的时间

谢谢您,请问如果在清理过程中,一个被标记为白色的对象被重新引用了,这个可达的对象岂不被删除了?

draveness commented 4 years ago

@draveness

您好,图7-31那里说,增量式会增加一次gc循环时间,请问这句话怎么理解呢

因为增量式的垃圾回收一定会引入额外开销(写屏障),所以一次 GC 的总时间会大于 STW GC 的时间

@draveness

您好,图7-31那里说,增量式会增加一次gc循环时间,请问这句话怎么理解呢

因为增量式的垃圾回收一定会引入额外开销(写屏障),所以一次 GC 的总时间会大于 STW GC 的时间

谢谢您,请问如果在清理过程中,一个被标记为白色的对象被重新引用了,这个可达的对象岂不被删除了?

你再看一下写屏障,写屏障的目的就是为了避免这种问题。

15389033811 commented 4 years ago

@draveness

@draveness

您好,图7-31那里说,增量式会增加一次gc循环时间,请问这句话怎么理解呢

因为增量式的垃圾回收一定会引入额外开销(写屏障),所以一次 GC 的总时间会大于 STW GC 的时间

@draveness

您好,图7-31那里说,增量式会增加一次gc循环时间,请问这句话怎么理解呢

因为增量式的垃圾回收一定会引入额外开销(写屏障),所以一次 GC 的总时间会大于 STW GC 的时间

谢谢您,请问如果在清理过程中,一个被标记为白色的对象被重新引用了,这个可达的对象岂不被删除了?

你再看一下写屏障,写屏障的目的就是为了避免这种问题。

清理阶段; 将状态切换至 _GCoff 开始清理阶段,初始化清理状态并关闭写屏障; 恢复用户程序,所有新创建的对象会标记成白色; 后台并发清理所有的内存管理单元,当 Goroutine 申请新的内存管理单元时就会触发清理;

当进入清理阶段时,有新的对象生成,会被标记成白色,那么说明这个对象会被回收,如果这个新对象有引用的话,他是不应该被清理的,不是么

draveness commented 4 years ago

清理阶段; 将状态切换至 _GCoff 开始清理阶段,初始化清理状态并关闭写屏障; 恢复用户程序,所有新创建的对象会标记成白色; 后台并发清理所有的内存管理单元,当 Goroutine 申请新的内存管理单元时就会触发清理;

当进入清理阶段时,有新的对象生成,会被标记成白色,那么说明这个对象会被回收,如果这个新对象有引用的话,他是不应该被清理的,不是么

扫描阶段结束之后,需要被清理的对象就确定了

15389033811 commented 4 years ago

o的三色标记法,最开始是为了引入增量垃圾收集而引入的,因为增量垃圾收集无法保存每段gc的执行进度 在1.5中引入了并发垃圾收集法,我想请教您,既然并发,负责gc的线程会一直保留gc的执行进度,因此可以使用两色,那么有新的对象就直接标记为黑色 我觉得需要三色的原因有二: 1.是在您博客中引出写屏障时的例子,某个对象的引用被重新指向了遍历过的对象,这钟情况双色没法再去遍历一次所有节点,会造成错删 2.当gc标记的cpu利用率低于程序创建新对象时,这时会借用程序的线程,此时多个线程使用双色标记法没法确定哪些需要遍历,哪些不需要遍历。

想问下您理解的对么,因为今天查了一天,三色标记法对并发的支持,没有很好的答案,所以请教您一下。

draveness commented 4 years ago

o的三色标记法,最开始是为了引入增量垃圾收集而引入的,因为增量垃圾收集无法保存每段gc的执行进度 在1.5中引入了并发垃圾收集法,我想请教您,既然并发,负责gc的线程会一直保留gc的执行进度,因此可以使用两色,那么有新的对象就直接标记为黑色 我觉得需要三色的原因有二: 1.是在您博客中引出写屏障时的例子,某个对象的引用被重新指向了遍历过的对象,这钟情况双色没法再去遍历一次所有节点,会造成错删 2.当gc标记的cpu利用率低于程序创建新对象时,这时会借用程序的线程,此时多个线程使用双色标记法没法确定哪些需要遍历,哪些不需要遍历。

想问下您理解的对么,因为今天查了一天,三色标记法对并发的支持,没有很好的答案,所以请教您一下。

我看了两遍你写的这一段话,你的问题是『三色标记法和并发垃圾收集有没有关系么』

15389033811 commented 4 years ago

@draveness

o的三色标记法,最开始是为了引入增量垃圾收集而引入的,因为增量垃圾收集无法保存每段gc的执行进度 在1.5中引入了并发垃圾收集法,我想请教您,既然并发,负责gc的线程会一直保留gc的执行进度,因此可以使用两色,那么有新的对象就直接标记为黑色 我觉得需要三色的原因有二: 1.是在您博客中引出写屏障时的例子,某个对象的引用被重新指向了遍历过的对象,这钟情况双色没法再去遍历一次所有节点,会造成错删 2.当gc标记的cpu利用率低于程序创建新对象时,这时会借用程序的线程,此时多个线程使用双色标记法没法确定哪些需要遍历,哪些不需要遍历。

想问下您理解的对么,因为今天查了一天,三色标记法对并发的支持,没有很好的答案,所以请教您一下。

我看了两遍你写的这一段话,你的问题是『三色标记法和并发垃圾收集有没有关系么』

【并发垃圾收集是否可以使用双色标记法,如果不行,为什么】这么讲应该好理解一些

draveness commented 4 years ago

@draveness

o的三色标记法,最开始是为了引入增量垃圾收集而引入的,因为增量垃圾收集无法保存每段gc的执行进度 在1.5中引入了并发垃圾收集法,我想请教您,既然并发,负责gc的线程会一直保留gc的执行进度,因此可以使用两色,那么有新的对象就直接标记为黑色 我觉得需要三色的原因有二: 1.是在您博客中引出写屏障时的例子,某个对象的引用被重新指向了遍历过的对象,这钟情况双色没法再去遍历一次所有节点,会造成错删 2.当gc标记的cpu利用率低于程序创建新对象时,这时会借用程序的线程,此时多个线程使用双色标记法没法确定哪些需要遍历,哪些不需要遍历。 想问下您理解的对么,因为今天查了一天,三色标记法对并发的支持,没有很好的答案,所以请教您一下。

我看了两遍你写的这一段话,你的问题是『三色标记法和并发垃圾收集有没有关系么』

【并发垃圾收集是否可以使用双色标记法,如果不行,为什么】这么讲应该好理解一些

双色标记法不能在标记的过程中被打断

liqingqiya commented 4 years ago

你好,大神,关于 “混合写屏障” 这里我一直有一个点想不通,所以实在是想问下。

为了移除栈的重扫描过程,除了引入混合写屏障之外,在垃圾收集的标记阶段,我们还需要将创建的所有新对象都标记成黑色,防止新分配的栈内存和堆内存中的对象被错误地回收,因为栈内存在标记阶段最终都会变为黑色,所以不再需要重新扫描栈空间。

我就是觉得混合写屏障好像也没法 解决重新扫描栈的问题。我举个例子: 现在有 A, B, C三个对象,A(黑色,栈上),B(灰色,栈上),C(白色,堆上);

当前引用关系是:

现在应用程序赋值修改,把A指向C:

由于A,B是栈上的对象,栈上对象赋值这里可是没有写屏障的;那么岂不是黑色对象指向白色对象了,C会回收了,就悬挂指针了???

大神,就指点!!!

whalecold commented 4 years ago

它就会保证开启写屏障时堆上所有对象的可达 看了下面的英文这句话感觉不是很准确,下面表达的意思是 它就会保证所有在开启写屏障时存活的对象可达即使他们被重写了

whalecold commented 4 years ago

关于Yuasa 删除写屏障这个我理解下来也是保证了强三色不变性(不知道是不是我理解错了),当一个被灰色引用的白色节点删除的时候,把白色染城灰色,这时候即使让这个节点被黑色的节点引用也不会出问题,符合了 黑色对象不会指向白色对象,只会指向灰色对象或者黑色对象 这个定义。 第一次看的时候被弱三色不变性这个定义弄的很迷糊,后面在别的地方看到关于 Yuasa 删除写屏障 的解释回头来看才理解了。

whalecold commented 4 years ago

关于Yuasa 删除写屏障这个我理解下来也是保证了强三色不变性(不知道是不是我理解错了),当一个被灰色引用的白色节点删除的时候,把白色染城灰色,这时候即使让这个节点被黑色的节点引用也不会出问题,符合了 黑色对象不会指向白色对象,只会指向灰色对象或者黑色对象 这个定义。 第一次看的时候被弱三色不变性这个定义弄的很迷糊,后面在别的地方看到关于 Yuasa 删除写屏障 的解释回头来看才理解了。

就是我理解错了,不好意思。提个小建议哈:图 7-29 Yuasa 删除写屏障 这里的 A 直接指向 D 更能解释 弱三色不变性 这个点,因为 Dijkstra 插入写屏障 也可以做到这样的实现。

draveness commented 4 years ago

你好,大神,关于 “混合写屏障” 这里我一直有一个点想不通,所以实在是想问下。

为了移除栈的重扫描过程,除了引入混合写屏障之外,在垃圾收集的标记阶段,我们还需要将创建的所有新对象都标记成黑色,防止新分配的栈内存和堆内存中的对象被错误地回收,因为栈内存在标记阶段最终都会变为黑色,所以不再需要重新扫描栈空间。

我就是觉得混合写屏障好像也没法 解决重新扫描栈的问题。我举个例子: 现在有 A, B, C三个对象,A(黑色,栈上),B(灰色,栈上),C(白色,堆上);

当前引用关系是:

  • A(黑) -> nil
  • B(灰) -> C(白)

现在应用程序赋值修改,把A指向C:

  • A(黑) -> C(白)
  • B(灰) -> nil

由于A,B是栈上的对象,栈上对象赋值这里可是没有写屏障的;那么岂不是黑色对象指向白色对象了,C会回收了,就悬挂指针了???

大神,就指点!!!

Goroutine 栈扫描的过程需要 STW,所以你描述的这种状况是不存在的,栈上的对象要么全白要么全黑,这个问题非常好,我最开始在这里也非常困惑。

liqingqiya commented 4 years ago

神,还有一点想再请教下。就是关于插入写屏障和删除写屏障。golang开始实现的是插入写屏障,后面实现的是混合写屏障。

插入写屏障:

  1. stw 扫描整个栈对象,确认root
  2. 扫描标记
  3. stw 重新扫描栈对象,和引用的对象链;

删除写屏障

  1. stw 扫描栈对象,确认root
  2. 扫描标记

混合写屏障:

  1. stw 扫描整个栈对象,确认root
  2. 扫描标记

从这里我们看到,其实三个都需要stw扫栈。我有两个点想不明白:

  1. 那为什么最开始golang用 插入写屏障,而不是删除写屏障呢?删除写屏障天然就不需要重新扫描栈。
  2. 直接用删除写屏障是不是就可以了,何必又要混合写屏障呢?
whalecold commented 4 years ago

神,还有一点想再请教下。就是关于插入写屏障和删除写屏障。golang开始实现的是插入写屏障,后面实现的是混合写屏障。

插入写屏障:

  1. stw 扫描整个栈对象,确认root
  2. 扫描标记
  3. stw 重新扫描栈对象,和引用的对象链;

删除写屏障

  1. stw 扫描栈对象,确认root
  2. 扫描标记

混合写屏障:

  1. stw 扫描整个栈对象,确认root
  2. 扫描标记

从这里我们看到,其实三个都需要stw扫栈。我有两个点想不明白:

  1. 那为什么最开始golang用 插入写屏障,而不是删除写屏障呢?删除写屏障天然就不需要重新扫描栈。
  2. 直接用删除写屏障是不是就可以了,何必又要混合写屏障呢?

好像是删除写屏障不止扫描栈,还要扫描堆,扫描完要做快照;而混合的话就不需要对堆做快照了,堆占用的空间往往也大于栈的。

draveness commented 4 years ago

神,还有一点想再请教下。就是关于插入写屏障和删除写屏障。golang开始实现的是插入写屏障,后面实现的是混合写屏障。

插入写屏障:

  1. stw 扫描整个栈对象,确认root
  2. 扫描标记
  3. stw 重新扫描栈对象,和引用的对象链;

删除写屏障

  1. stw 扫描栈对象,确认root
  2. 扫描标记

混合写屏障:

  1. stw 扫描整个栈对象,确认root
  2. 扫描标记

从这里我们看到,其实三个都需要stw扫栈。我有两个点想不明白:

  1. 那为什么最开始golang用 插入写屏障,而不是删除写屏障呢?删除写屏障天然就不需要重新扫描栈。
  2. 直接用删除写屏障是不是就可以了,何必又要混合写屏障呢?

结合两者可以避免栈的重扫,也可以减少一部分冗余的扫描和标记操作。

tumayun commented 4 years ago

我们将这种错误成为悬挂指针 --> 我们将这种错误称为悬挂指针


2020 05-13 UPDATES: 已修复

tumayun commented 4 years ago

Dijkstra 必须为栈上的对象增加写屏障或者在标记阶段完成重新对栈上的对象对象进行扫描


2020 05-13 UPDATES: 已修复

Kuri-su commented 4 years ago

在插入写屏障那一节的伪代码好像有点问题, 原文:

writePointer(slot, ptr):
    shade(ptr)
    *field = ptr

似乎应该修改为

writePointer(slot, ptr):
    shade(ptr)
    *slot = ptr

2020 05-28 UPDATES: 已修复

liqingqiya commented 4 years ago

@draveness

你好,大神,关于 “混合写屏障” 这里我一直有一个点想不通,所以实在是想问下。

为了移除栈的重扫描过程,除了引入混合写屏障之外,在垃圾收集的标记阶段,我们还需要将创建的所有新对象都标记成黑色,防止新分配的栈内存和堆内存中的对象被错误地回收,因为栈内存在标记阶段最终都会变为黑色,所以不再需要重新扫描栈空间。

我就是觉得混合写屏障好像也没法 解决重新扫描栈的问题。我举个例子: 现在有 A, B, C三个对象,A(黑色,栈上),B(灰色,栈上),C(白色,堆上);

当前引用关系是:

  • A(黑) -> nil
  • B(灰) -> C(白)

现在应用程序赋值修改,把A指向C:

  • A(黑) -> C(白)
  • B(灰) -> nil

由于A,B是栈上的对象,栈上对象赋值这里可是没有写屏障的;那么岂不是黑色对象指向白色对象了,C会回收了,就悬挂指针了???

大神,就指点!!!

Goroutine 栈扫描的过程需要 STW,所以你描述的这种状况是不存在的,栈上的对象要么全白要么全黑,这个问题非常好,我最开始在这里也非常困惑。

大神。这里我回去看了一遍代码,这里还是残留问题。你说的“栈上的对象要么全白,要么全黑“ ,这个只是对一个 goroutine 栈来说的(golang 暂停业务扫描栈也是一个一个来的)。如果场景是 A 在 Goroutine1,B在Goroutine2呢?这种情况就是A是黑色,B是白色或者灰色。这样会不会就有我说的原本那个问题呢?

draveness commented 4 years ago

大神。这里我回去看了一遍代码,这里是残留问题。你说的“栈上的对象要么全白,要么全黑“ ,这个只是对一个 goroutine 栈来说的(golang 暂停业务扫描栈也是一个一个来的)。如果场景是 A 在 Goroutine1,B在Goroutine2呢?这种情况就是A是黑色,B是白色或者灰色。这样会不会就有我说的原本那个问题呢?

你觉得一个栈上的对象引用另个栈上对象并且不触发逃逸可能么🙂

liqingqiya commented 4 years ago

大神。这里我回去看了一遍代码,这里是残留问题。你说的“栈上的对象要么全白,要么全黑“ ,这个只是对一个 goroutine 栈来说的(golang 暂停业务扫描栈也是一个一个来的)。如果场景是 A 在 Goroutine1,B在Goroutine2呢?这种情况就是A是黑色,B是白色或者灰色。这样会不会就有我说的原本那个问题呢?

你觉得一个栈上的对象引用另个栈上对象并且不触发逃逸可能么🙂

大神,我的意思是A是g1栈上对象,B是g2栈上对象,C是堆上对象。A和B都只是引用C的,而不是A引用B。这种情况也会导致A,B分配到堆上吗?

draveness commented 4 years ago

大神。这里我回去看了一遍代码,这里是残留问题。你说的“栈上的对象要么全白,要么全黑“ ,这个只是对一个 goroutine 栈来说的(golang 暂停业务扫描栈也是一个一个来的)。如果场景是 A 在 Goroutine1,B在Goroutine2呢?这种情况就是A是黑色,B是白色或者灰色。这样会不会就有我说的原本那个问题呢?

你觉得一个栈上的对象引用另个栈上对象并且不触发逃逸可能么🙂

大神,我的意思是A是g1栈上对象,B是g2栈上对象,C是堆上对象。A和B都只是引用C的,而不是A引用B。这种情况也会导致A,B分配到堆上吗?

想要实现你在这里描述的场景,我们需要满足以下条件:

g1 栈上的黑色对象 A 引用白色对象 C 需要持有 g2 栈上的对象 B 或者 对象 C

所以你在这里描述的场景是不可能出现的。

Bububuger commented 4 years ago

请教一个问题,混合写屏障也面临着“为栈上的对象增加写屏障或者在标记阶段完成重新对栈上的对象进行扫描”的问题吧,只不过当初选择了后者,现在选择了前者?

draveness commented 4 years ago

请教一个问题,混合写屏障也面临着“为栈上的对象增加写屏障或者在标记阶段完成重新对栈上的对象进行扫描”的问题吧,只不过当初选择了后者,现在选择了前者?

现在的做法是 STW 扫描一次协程栈 + 混合写屏障 + 创建对象默认黑色

Bububuger commented 4 years ago

@draveness

请教一个问题,混合写屏障也面临着“为栈上的对象增加写屏障或者在标记阶段完成重新对栈上的对象进行扫描”的问题吧,只不过当初选择了后者,现在选择了前者?

现在的做法是 STW 扫描一次协程栈 + 混合写屏障 + 创建对象默认黑色

多谢。 我没完全理解引入混合写屏障的出发点。如果说省略了栈的重扫,这个主要是因为这次选择了扫描栈的时候开启混合写屏障。而且混合写屏障本身的操作是写屏障操作的两倍。

draveness commented 4 years ago

@draveness

请教一个问题,混合写屏障也面临着“为栈上的对象增加写屏障或者在标记阶段完成重新对栈上的对象进行扫描”的问题吧,只不过当初选择了后者,现在选择了前者?

现在的做法是 STW 扫描一次协程栈 + 混合写屏障 + 创建对象默认黑色

我没完全理解引入混合写屏障的出发点。如果说省略了栈的重扫,这个主要是因为这次选择了扫描栈的时候开启混合写屏障。而且混合写屏障本身的操作是写屏障操作的两倍。

引入混合写屏障虽然会增加写屏障的额外开销,但是可以解决重新扫描的问题,能够减少程序的最大暂停时间。

Bububuger commented 4 years ago

@draveness

请教一个问题,混合写屏障也面临着“为栈上的对象增加写屏障或者在标记阶段完成重新对栈上的对象进行扫描”的问题吧,只不过当初选择了后者,现在选择了前者?

现在的做法是 STW 扫描一次协程栈 + 混合写屏障 + 创建对象默认黑色

我没完全理解引入混合写屏障的出发点。如果说省略了栈的重扫,这个主要是因为这次选择了扫描栈的时候开启混合写屏障。而且混合写屏障本身的操作是写屏障操作的两倍。

引入混合写屏障虽然会增加写屏障的额外开销,但是可以解决重新扫描的问题,能够减少程序的最大暂停时间。

明白了!最后一个疑问,现在的做法是 STW 扫描一次协程栈 + 混合写屏障 + 创建对象默认黑色这里创建对象是指所有吗(栈、堆)不是单独指栈上(只是栈上貌似会有问题)

draveness commented 4 years ago

@draveness

请教一个问题,混合写屏障也面临着“为栈上的对象增加写屏障或者在标记阶段完成重新对栈上的对象进行扫描”的问题吧,只不过当初选择了后者,现在选择了前者?

现在的做法是 STW 扫描一次协程栈 + 混合写屏障 + 创建对象默认黑色

我没完全理解引入混合写屏障的出发点。如果说省略了栈的重扫,这个主要是因为这次选择了扫描栈的时候开启混合写屏障。而且混合写屏障本身的操作是写屏障操作的两倍。

引入混合写屏障虽然会增加写屏障的额外开销,但是可以解决重新扫描的问题,能够减少程序的最大暂停时间。

明白了!最后一个疑问,现在的做法是 STW 扫描一次协程栈 + 混合写屏障 + 创建对象默认黑色这里创建对象是指所有吗(栈、堆)不是单独指栈上(只是栈上貌似会有问题)

是的,开启写屏障期间创建的所有对象默认都是黑色

bluntdel commented 4 years ago

神,请教个问题: 写屏障伪代码里的slot和ptr到底应该怎么理解?我看注释里: // slot is the destination in Go code. // ptr is the value that goes into the slot in Go code. 例如A = &B, 那应该slot是B,ptr是A。那么Dijkstra写屏障里, 由A -> B,变成 A -> C里,shade应该是ptr,也就是A,但是这里的A已经是黑色的。如何shade呢? 如果不是这么理解,那slot应该怎么理解呢,能举个例子吗?

draveness commented 4 years ago

神,请教个问题: 写屏障伪代码里的slot和ptr到底应该怎么理解?我看注释里: // slot is the destination in Go code. // ptr is the value that goes into the slot in Go code. 例如A = &B, 那应该slot是B,ptr是A。那么Dijkstra写屏障里, 由A -> B,变成 A -> C里,shade应该是ptr,也就是A,但是这里的A已经是黑色的。如何shade呢? 如果不是这么理解,那slot应该怎么理解呢,能举个例子吗?

可以理解成 object.slot = ptr

bluntdel commented 4 years ago

@draveness

神,请教个问题: 写屏障伪代码里的slot和ptr到底应该怎么理解?我看注释里: // slot is the destination in Go code. // ptr is the value that goes into the slot in Go code. 例如A = &B, 那应该slot是B,ptr是A。那么Dijkstra写屏障里, 由A -> B,变成 A -> C里,shade应该是ptr,也就是A,但是这里的A已经是黑色的。如何shade呢? 如果不是这么理解,那slot应该怎么理解呢,能举个例子吗?

可以理解成 object.slot = ptr

我看了很多,我觉得writePointer(slot,ptr)这样理解会好一点? 添加下游对象(当前下游对象slot, 新下游对象ptr) {
//1 标记灰色(新下游对象ptr)

//2 当前下游对象slot = 新下游对象ptr
} 例如A = &B;slot指的就是A的内存槽对应的位置存储的东西,ptr就是指向B的指针。dijstra屏障中,如果ptr为白色(即ptr指向的对象为白色),就将ptr指向的对象shade。 其实就是发生引用的时候,如果发现被引用的对象是白色就将它变成灰色。 所谓的ptr的颜色应该指的就是ptr指向的对象的颜色?

bluntdel commented 4 years ago

还有三个问题请教下,十分感谢: 1.混合屏障相对 Yuasa屏障的优势就是 不需要GC开始时的STW对吧? 那为什么 Yuasa的删除写屏障则需要在GC开始时STW扫描堆栈来记录初始快照? 2.混合屏障相对于Dijsktra屏障的优势是不需要Rescan,而Rescan是因为在栈上创建的对象为白色造成的,所以混合屏障中创建的对象都为黑色。那么Dijsktra+栈上对象标黑就能完成,为什么还需要Yuasa屏障。 3.既然堆上的对象都是有写屏障,那创建时为什么还要标黑呢?

yanjinbin commented 4 years ago

👍🏻 读完了 花了1天 起码4--6h工时 慢慢看完的,也部分对照了下博主对源码注释的翻译。翻译的很棒吧。不过,有个疑问 shade这个函数的意思应该不是染成灰色的意思吧? 感觉中文解释有点勉强,不能表现整个意思

Shade indicates that it has seen a white pointer by adding the referent to wbuf as well as marking it

瑕不掩瑜,反正算是整体过了一遍,gc设计的这么复杂,博主对于GC分享,工作做的还是挺多的!

唯一的遗憾就是 如果问哪些设计细节,如何汇聚搭建了go gc的整体框架,还是要多看看proposal design talk,博主这篇能起到一个dictionary的作用😄

gasxia commented 4 years ago

该方法在调用时会阻塞调用方"知道"当前垃圾收集循环完成

直到


2020-10-15 UPDATES: 已修复

wutsol commented 4 years ago

请教一下删除写屏障的问题,shade(*slot)不应该是把原对象染成灰色吗,例如图中A的指向从B改成C,我理解的应该是将A染色,而不是B,这样A是灰色,C是白色,是满足弱三色不变性的

draveness commented 4 years ago

@wutsol 请教一下删除写屏障的问题,shade(*slot)不应该是把原对象染成灰色吗,例如图中A的指向从B改成C,我理解的应该是将A染色,而不是B,这样A是灰色,C是白色,是满足弱三色不变性的

最好再看一下删除写屏障的部分,我这里简单解释一下为啥你提的方案有问题,假设存在以下的对象关系,角标 w、g、b 分别表示白色、灰色和黑色

A_g-> B_w -> C_w

我们分别讨论以下三种情况的垃圾回收过程

  1. 不开启写屏障
  2. 使用你提出的方案
  3. 使用删除写屏障

不开启写屏障

  1. Collector:扫描 A 对象:
A_b-> B_g -> C_w
  1. Mutator:建立 A -> C 的指针
A_b-> B_g -> C_w
  |           ^
  ------------|
  1. Mutator:删除 B -> C 的指针
A_b-> B_g    C_w
  |           ^
  ------------|

结论:C 对象白色没有被灰色对象引用被垃圾回收

你提出的方法

  1. Collector:扫描 A 对象:
A_b-> B_g -> C_w
  1. Mutator:建立 A -> C 的指针
A_b-> B_g -> C_w
  |           ^
  ------------|
  1. Mutator:删除 B -> C 的指针,B 被标记成灰色
A_b-> B_g    C_w
  |           ^
  ------------|

结论:C 对象白色仍然没有被灰色对象引用被垃圾回收

删除写屏障

  1. Collector:扫描 A 对象:
A_b-> B_g -> C_w
  1. Mutator:建立 A -> C 的指针
A_b-> B_g -> C_w
  |           ^
  ------------|
  1. Mutator:删除 B -> C 的指针,C 对象被标记成灰色
A_b-> B_g    C_g
  |           ^
  ------------|

结论:C 对象不会被回收

wutsol commented 4 years ago

@draveness

@wutsol 请教一下删除写屏障的问题,shade(*slot)不应该是把原对象染成灰色吗,例如图中A的指向从B改成C,我理解的应该是将A染色,而不是B,这样A是灰色,C是白色,是满足弱三色不变性的

最好再看一下删除写屏障的部分,我这里简单解释一下为啥你提的方案有问题,假设存在以下的对象关系,角标 w、g、b 分别表示白色、灰色和黑色

A_g-> B_w -> C_w

我们分别讨论以下三种情况的垃圾回收过程

  1. 不开启写屏障
  2. 使用你提出的方案
  3. 使用删除写屏障

不开启写屏障

  1. Collector:扫描 A 对象:
A_b-> B_g -> C_w
  1. Mutator:建立 A -> C 的指针
A_b-> B_g -> C_w
  |           ^
  ------------|
  1. Mutator:删除 B -> C 的指针
A_b-> B_g    C_w
  |           ^
  ------------|

结论:C 对象白色没有被灰色对象引用被垃圾回收

你提出的方法

  1. Collector:扫描 A 对象:
A_b-> B_g -> C_w
  1. Mutator:建立 A -> C 的指针
A_b-> B_g -> C_w
  |           ^
  ------------|
  1. Mutator:删除 B -> C 的指针,B 被标记成灰色
A_b-> B_g    C_w
  |           ^
  ------------|

结论:C 对象白色仍然没有被灰色对象引用被垃圾回收

删除写屏障

  1. Collector:扫描 A 对象:
A_b-> B_g -> C_w
  1. Mutator:建立 A -> C 的指针
A_b-> B_g -> C_w
  |           ^
  ------------|
  1. Mutator:删除 B -> C 的指针,A 对象被重新标记成灰色
A_g-> B_g    C_w
  |           ^
  ------------|

结论:白色的对象仍然被灰色的 A 对象引用,C 对象不会被回收

还是没怎么懂,为什么删除B -> C的指针,会和A有关?我重看了一遍,我理解的是把*slot染色,也就是老对象染色,但你说的删除B->C为啥会染色A,不应该是把C染成灰色嘛

draveness commented 4 years ago

还是没怎么懂,为什么删除B -> C的指针,会和A有关?我重看了一遍,我理解的是把*slot染色,也就是老对象染色,但你说的删除B->C为啥会染色A,不应该是把C染成灰色嘛

这个地方写错了,C 对象会被染色

kilosliu commented 3 years ago

大神,我想问一下,回收堆目标那个位置,并发的complete完成是指 标记完成 还是 清除完成?如果是清除完成那不是还没有到目标就完成一轮垃圾收集了

draveness commented 3 years ago

@Akatsukizzz 大神,我想问一下,回收堆目标那个位置,并发的complete完成是指 标记完成 还是 清除完成?如果是清除完成那不是还没有到目标就完成一轮垃圾收集了

是指标记完成,每轮 GC 可以理解为以清除开始

the first step of GC was to finish sweeping any unswept parts of the heap - https://docs.google.com/document/d/1wmjrocXIWTr1JxU-3EQBI6BK6KgtiFArkG47XK73xIQ/edit

kilosliu commented 3 years ago

@draveness

@Akatsukizzz 大神,我想问一下,回收堆目标那个位置,并发的complete完成是指 标记完成 还是 清除完成?如果是清除完成那不是还没有到目标就完成一轮垃圾收集了

是指标记完成,每轮 GC 可以理解为以清除开始

the first step of GC was to finish sweeping any unswept parts of the heap - https://docs.google.com/document/d/1wmjrocXIWTr1JxU-3EQBI6BK6KgtiFArkG47XK73xIQ/edit

所以正常的GC流程是不是还是标准的 先标记,再清除,然后标记之前会检查上一轮的清除是否结束,没结束就会帮忙清除还未清除的内存管理单元,如果已经结束就重新进入标记阶段也就是新的一轮GC。

然后回收堆目标那个位置是指在达到用户设定的GOGC目标时 开始清除 而不是开始GC

kilosliu commented 3 years ago

@draveness

@Akatsukizzz 大神,我想问一下,回收堆目标那个位置,并发的complete完成是指 标记完成 还是 清除完成?如果是清除完成那不是还没有到目标就完成一轮垃圾收集了

是指标记完成,每轮 GC 可以理解为以清除开始

the first step of GC was to finish sweeping any unswept parts of the heap - https://docs.google.com/document/d/1wmjrocXIWTr1JxU-3EQBI6BK6KgtiFArkG47XK73xIQ/edit

我把那个文章里最终实现的部分看了,所以流程应该是开始GC的标记之前需要保证上一轮的GC任务全部完成,将整个堆需要清理的部分全部清理,之后才能开启下一轮的标记,因为GC流程最后的清理是惰性清理的,是在每次分配内存时清理一部分,然后在下一轮开始标记前才清理整个堆,现在我理解为什么实现原理里的过程第一步是清除终止了。感谢博主

Napoleon0621 commented 3 years ago

“写屏障是保证 Go 语言并发标记安全不可获取的技术,我们需要使用混合写屏障维护对象图的弱三色不变性,然而写屏障的实现需要编译器和运行时的共同协作。” Typo: “不可获取” -> “不可或缺”


2020-11-15 UPDATES: 已修复

GavinXu520 commented 3 years ago

有个疑问,当【标记终止】时,会关闭混合写屏障、唤醒所有协助垃圾收集的用户程序、恢复用户 Goroutine 的调度。那么在这个阶段 由于 混合写屏障【关闭】了,而被恢复的 G 又可能产生新的 对象,这些对象 在这个阶段怎么处理的?是全部都是 白色么?

draveness commented 3 years ago

有个疑问,当【标记终止】时,会关闭混合写屏障、唤醒所有协助垃圾收集的用户程序、恢复用户 Goroutine 的调度。那么在这个阶段 由于 混合写屏障【关闭】了,而被恢复的 G 又可能产生新的 对象,这些对象 在这个阶段怎么处理的?是全部都是 白色么?

标记终止就进入 Sweep 阶段了,这个时候就没有颜色了,内存分配器会在分配的路径上清除垃圾

roubaozitm commented 3 years ago

作者您好,我对着代码看了您的文章,写的很赞。 现在有个问题想请教一下。我看到的是目前的垃圾回收会在gcStart函数里面会STW。我想问下这里STW是只停止当前协程吗

GavinXu520 commented 3 years ago

@draveness

有个疑问,当【标记终止】时,会关闭混合写屏障、唤醒所有协助垃圾收集的用户程序、恢复用户 Goroutine 的调度。那么在这个阶段 由于 混合写屏障【关闭】了,而被恢复的 G 又可能产生新的 对象,这些对象 在这个阶段怎么处理的?是全部都是 白色么?

标记终止就进入 Sweep 阶段了,这个时候就没有颜色了,内存分配器会在分配的路径上清除垃圾

那就是说sweep阶段中的G分配的新对象,对于这些对象的回收控制 是直接交给了 内存分配器自行处理么?