共计 2357 个字符,预计需要花费 6 分钟才能阅读完成。
go runtime 主要由C语言编写,go1.5之后开始自举,它是每个GO包的最顶级包,代码在$GOROOT/src/runtime目录下。
http://books.studygolang.com/the-way-to-go_ZH_CN/02.7.html
一、GO Runtime是什么?
go没有像Java,.NET有虚拟机,而是和C/C++一样可以直接编译成可执行文件,runtime是会和用户代码一起编译到这个可执行文件中,所以部署时会比Java、Python轻松的多,不需要依赖任何其他文件。
Golang Runtime是go语言运行时所需要的基础设施。
主要包含以下部分:
- 内存分配
- 垃圾回收(GC)
- 栈处理
- goroutine调度
- channel、slice、map、reflection等的实现
go的可执行文件包含了用户程序+runtime,与操作系统内核之间的关系如下图。

二、内存分配
2.1 Tcmalloc 算法
Tcmalloc(Thread Caching Malloc)是 google 为 C语言 开发的运行时内存分配算法,其核心思想就是将可用的堆内存采用多级管理,每个线程维护一个独立的内存池,进行内存分配时,优先从内存池里分配,内存池不够用时,才会从全局内存池申请,减少了访问全局内存的频率,从而降低锁的粒度。
Go 的 runtime 的内存分配采用的是类似Tcmalloc的这种结构。
2.2 golang 内存分配
1.10版本及之前
在Linux arm64系统里,Go 程序在启动时,会首先向系统申请一块内存(虚拟内存地址),然后切成小块来进行管理,分为3个区域:spans
、bitmap
、arena

- arena:堆区,go runtime 在动态分配的内存都在这个区域,最大可扩展到512GB,并且将内存块分成 8kb 的页,一些组合起来的称为
mspan
,成为 go 中内存管理的基本单元,这种连续的页一般是操作系统的内存页几倍的大小 - bitmap:主要用来GC,标记堆区使用的映射表(用两个bit表示arena中一个字的可用状态),它记录了那些区域保存了对象,对象是否包含指针,以及 GC 的标记信息,可达16GB((512GB/8字节一个字)*2/8bit每个字节=16GB)
- spans:存放
mspan
的指针,最大可达512MB(512GB/8KB*指针大小8byte=512MB),根据spans
区域的信息可以很容易找到mspan
,它可以在 GC 时更快的找到大块的内存mspan
1.11版本及之后
改成了两阶稀疏索引的方式,内存管理可用超过512GB,最大可达256TB,内存空间扩展时也可以运行不连续。mheap
中的arenas
变成一个二阶数组,一阶只有一个slot
(槽),二阶有4M个slot
,每个可以管理16M的内存,总共可以管理 4M*16M=256TB内存。bitmap
和spans
和之前一致。
三、垃圾回收GC
3.1 STW (stop the world)
说GC之前,要先说下STW
。不然很多小伙伴可能不明白高级语言的GC玩的那么花的目的是什么。
STW,全称是 stop the world
。字面意思是停止整个世界,也挺形象的。实际指的是在GC
事件发生的过程中,会产生停顿。而这个停顿产生的时候,整个应用程序的线程都会被暂停,没有任何响应,只有负责GC
的线程继续工作。这种停顿的现象就叫做STW
。
记住,所有的GC
机制都有 STW
。所以,各种 GC 算法优化的重点都是减少 STW,Java程序调优JVM的一个重点就是这个。
3.2 Golang 的三色标记法
三色标记法其实也是一种标记-清除算法(mark & sweep
)。
相对于普通的标记清除算法,golang
进行了改进,采用三色标记法+写屏障
来减少STW
的时间。
三色标记法
- 所有对象最开始的都是白色的
- 从每组对象的
root
开始找到所有可达对象,标记为灰色
,放入灰色待处理队列 - 遍历所有灰色对象队列,将其引用的对象标记为
灰色
,放入灰色待处理队列,自身标记为黑色
- 循环第三步,直至所有灰色队列为空。此时所有引用对象都被标记为
黑色
,所有不可达对象依然为白色
,而白色
就是需要进行回收的对象
标记的过程是不需要 STW 的,与应用程序的线程是并发执行的,从而大大的缩短了 STW 的时间。

最开始,A~F对象都是白色的。
接着从这组对象的root往下寻找可达对象,发现了ABC对象
,标记为灰色,将其放入灰色待处理队列
然后扫描ABC对象
,发现B无引用对象,标记为黑色。A引用了D,C引用了E,故将AC标记黑色,DE标记为灰色继续分析
再接着扫描DE对象,发现没有引用对象,标记为黑色,放入黑色队列
最后将所有白色对象回收

写屏障
因为三色标记的过程和程序是并行的,所以在标记的过程中,如果有新的引用产生,就可能导致误清除(即黑色对象又引用了白色对象
)。golang采用了写屏障机制,就是在内存写操作前,维护一个约束,从而确保黑色对象不能引用白色对象。
GC触发条件
- 内存分配达到一定比例
- 2分钟没有触发过GC
- 手动触发,调用
runtime.GC()
思考问题:golang垃圾回收机制中怎么判断一个对象是不是根对象呢?
根对象的判断通常是由 Go 运行时(runtime)来完成的。在 GC 开始时,运行时系统会识别出所有的根对象,并将它们标记为灰色。这些根对象通常包括:
全局变量:所有在包级别声明的变量都是根对象。
当前协程(goroutine)的栈:每个活跃的协程都有一个栈,栈上的局部变量也是根对象。
寄存器和线程本地存储(TLS):某些特定的寄存器值和线程本地存储也可能被视为根对象。
垃圾回收器会从这些根对象开始,通过引用关系遍历对象的图,将所有可达的对象标记为黑色,最终清理掉所有未被标记(即白色)的对象。
四、Goroutine调度模型
详见本站的另一篇文章 GMP调度模型详解