Illustration created for “A Journey With Go”, made from the original Go Gopher, created by Renee French.git
这篇文章基于 Go 1.13 版本。有关内存管理的讨论在个人文章 ”Go:内存管理与分配 ” 中有解释。github
清理内存是一个过程,它可以让 Go 知道哪些内存段最近可用于分配。可是,它并不会使用将位置 0 的方式来清理内存。golang
将内存置 0 的过程 —— 就是把内存段中的全部位赋值为 0 —— 是在分配过程当中即时执行的。缓存
Zeroing the memory性能
可是,咱们可能想知道 Go 采用什么样的策略去知道哪些对象可以用于分配。因为在每一个范围内有一个内部位图 allocBits
,Go 实际上会追踪那些空闲的对象。让咱们从初始态开始来回顾一下它的工做流程,spa
Free objects tracking with allocBitscode
就性能角度来看,allocBits
表明了一个初始态而且会保持不变,可是它会由 freeIndex
(一个指向第一个空闲位置的增量计数器)所协助。对象
而后,第一个分配就开始了:内存
Free objects tracking with allocBits资源
freeIndex
如今增长了,而且基于 allocBits
知道了下一段空闲位置。
分配过程将会再一次出现,以后, GC 将会启动去释放再也不被使用的内存。在标记期间,GC 会用一个位图 gcmarkBits
来跟踪在使用中的内存。让咱们经过咱们运行的程序以相同的示例为例,在第一个块再也不被使用的地方。
Memory tracking during the garbage collector
正在被使用的内存被标记为黑色,然而当前执行并不可以到达的那些内存会保持为白色。
有关更多关于标记和着色阶段的信息,我建议你阅读个人这篇文章 Go:GC 是如何标记内存的? 如今,咱们可使用
gomarkBits
精确查看可用于分配的内存。Go 如今也使用gomarkBits
代替了allocBits
,这个操做就是内存清理:
Sweeping a span
可是,这必须在每个范围内执行完毕而且会花费许多时间。Go 的目标是在清理内存时不阻碍执行,并为此提供了两种策略。
Go 提供了两种方式来清理内存:
关于后台工做程序,当开始运行程序时,Go 将设置一个后台运行的 Worker(惟一的任务就是去清理内存),它将进入睡眠状态并等待内存段扫描:
Background sweeper
经过追踪过程的周期,咱们也能看到这个后台工做程序老是出现去清理内存:
Background sweeper
清理内存段的第二种方式是即时执行。可是,因为这些内存段已经被分发到每个处理器的本地缓存 mcache
中,所以很难追踪首先清理哪些内存。这就是为何 Go 首先将全部内存段移动到 mcentral
的缘由。
Spans are released to the central list
而后,它将会让本地缓存 mcache
再次请求它们,去即时清理:
Sweep span on the fly during allocation
即时扫描确保全部内存段在保存资源的过程当中都会获得清理,同时会保存资源以及不会阻塞程序执行。
正如以前看到的,因为后台只有一个 worker 在清理内存块,清理过程可能会花费一些时间。可是,咱们可能想知道若是另外一个 GC 周期在一次清理过程当中启动会发生什么。在这种状况下,这个运行 GC 的 Goroutine 就会在开始标记阶段前去协助完成剩余的清理工做。让咱们举个例子看一下连续调用两次 GC,包含数千个对象的内存分配的过程。
Sweeping must be finished before a new cycle
可是,若是开发者没有强制调用 GC,这个状况并不会发生。在后台运行的清理工做以及在执行过程当中的清理工做应该足够多,由于清理内存块的数量和去触发一个新的周期(译者注:GC 周期)的所需的分配的数量成正比。
做者:Vincent Blanchon 译者:sh1luo 校对:polaris1119