Golang运行时(Runtime)解析

共计 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语言运行时所需要的基础设施。

主要包含以下部分:

  1. 内存分配
  2. 垃圾回收(GC)
  3. 栈处理
  4. goroutine调度
  5. channel、slice、map、reflection等的实现

go的可执行文件包含了用户程序+runtime,与操作系统内核之间的关系如下图。

Golang运行时(Runtime)解析

二、内存分配

2.1 Tcmalloc 算法

Tcmalloc(Thread Caching Malloc)是 google 为 C语言 开发的运行时内存分配算法,其核心思想就是将可用的堆内存采用多级管理,每个线程维护一个独立的内存池,进行内存分配时,优先从内存池里分配,内存池不够用时,才会从全局内存池申请,减少了访问全局内存的频率,从而降低锁的粒度
Go 的 runtime 的内存分配采用的是类似Tcmalloc的这种结构。

2.2 golang 内存分配

1.10版本及之前

在Linux arm64系统里,Go 程序在启动时,会首先向系统申请一块内存(虚拟内存地址),然后切成小块来进行管理,分为3个区域:spansbitmaparena

Golang运行时(Runtime)解析
  • 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内存。bitmapspans和之前一致。

三、垃圾回收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的时间。

三色标记法

  1. 所有对象最开始的都是白色的
  2. 从每组对象的root开始找到所有可达对象,标记为灰色,放入灰色待处理队列
  3. 遍历所有灰色对象队列,将其引用的对象标记为灰色,放入灰色待处理队列,自身标记为黑色
  4. 循环第三步,直至所有灰色队列为空。此时所有引用对象都被标记为黑色,所有不可达对象依然为白色,而白色就是需要进行回收的对象

标记的过程是不需要 STW 的,与应用程序的线程是并发执行的,从而大大的缩短了 STW 的时间。

Golang运行时(Runtime)解析

最开始,A~F对象都是白色的。

接着从这组对象的root往下寻找可达对象,发现了ABC对象,标记为灰色,将其放入灰色待处理队列

然后扫描ABC对象,发现B无引用对象,标记为黑色。A引用了D,C引用了E,故将AC标记黑色,DE标记为灰色继续分析

再接着扫描DE对象,发现没有引用对象,标记为黑色,放入黑色队列

最后将所有白色对象回收

Golang运行时(Runtime)解析

写屏障

因为三色标记的过程和程序是并行的,所以在标记的过程中,如果有新的引用产生,就可能导致误清除(即黑色对象又引用了白色对象)。golang采用了写屏障机制,就是在内存写操作前,维护一个约束,从而确保黑色对象不能引用白色对象。

GC触发条件

  1. 内存分配达到一定比例
  2. 2分钟没有触发过GC
  3. 手动触发,调用runtime.GC()

思考问题:golang垃圾回收机制中怎么判断一个对象是不是根对象呢?

根对象的判断通常是由 Go 运行时(runtime)来完成的。在 GC 开始时,运行时系统会识别出所有的根对象,并将它们标记为灰色。这些根对象通常包括:

全局变量:所有在包级别声明的变量都是根对象。
当前协程(goroutine)的栈:每个活跃的协程都有一个栈,栈上的局部变量也是根对象。
寄存器和线程本地存储(TLS):某些特定的寄存器值和线程本地存储也可能被视为根对象。

垃圾回收器会从这些根对象开始,通过引用关系遍历对象的图,将所有可达的对象标记为黑色,最终清理掉所有未被标记(即白色)的对象。

四、Goroutine调度模型

详见本站的另一篇文章 GMP调度模型详解

正文完
 
Dustin
版权声明:本站原创文章,由 Dustin 2022-11-08发表,共计2357字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。