每日一Go-28、Go语言进阶-深入Go运行时:内存管理与GC

0 阅读4分钟

图片

文末有源码下载链接!

    Go运行时(runtime)是Go高性能和高并发的核心支撑,其中内存管理与垃圾回收是关键。今天将深入底层机制,理解Go程序如何分配内存、如何决定数据的生命周期、以及Go垃圾回收器是如何工作的。

1、Go内存管理体系概览

Go使用一种分层式、针对并发优化的内存管理架构。可以概括为:线程本地分配(arena+mcache)+全局分配(central)+垃圾回收(GC),也就是减少锁争用+高速内存分配+自动回收。

2、内存分配器

Go的内存分配器模仿TCMalloc(TCMalloc—Design document for the C/C++ memory allocator TCMalloc, which the Go memory allocator is based on.),主要分三层:

2.1 堆(Heap):有runtime管理的大块连续内存区域

Go并不直接向操作系统请求小块内存,而是向操作系统申请一块(64MB大小)内存,这块内存在操作系统的术语叫Arena(竞技场),Go在Arena内做更细粒度的管理

2.2 中心缓存(Central Cache):全局共享的小对象池(加锁)

Central按size class将对象划分为8B~32KB的多种规格:

每个size class都有一个central列表

用于分配中等频率的内存

有锁(mutex)保证多线程安全

2.3 mcache:每个P拥有的线程本地缓存(无锁)

Go的GMP模型中,每个P(处理器)拥有一个mcache:

本地缓存,分配速度极快

小对象分配的时间复杂度是O(1),直接从mcache中拿

mcache没了才从Central Cache获取

3、小对象和大对象的分配策略

对象大小分配方式
<=32KBmcache中的小对象快速分配
>32KB大对象,直接在堆heap上分配

4、栈内存和堆内存

Go使用可憎长栈(由2KB~8GB):

栈内存快、无需GC,函数返回自动销毁

堆内存慢,需要GC管理

因此,尽可能把对象放在栈内存上,更高效,要做到这一点,依赖逃逸分析。

5、逃逸(Escape)分析

什么是逃逸?编译器决定变量放在栈上还是放在堆上,放在堆上的就产生了逃逸。可以用以下命令来查看自己的程序哪些地方产生了逃逸:

go
"-m -m"

图片

5.1 什么情况下变量会逃逸?

5.1.1 返回值逃出当前作用域

图片

5.1.2 变量被存到interface、空interface时

图片

5.1.3 闭包引用的外部变量

图片

5.1.4 大量数据复制到channel时也可能逃逸

5.1.5 编译器无法证明变量的生命周期,例如发送指针到通道

图片

6、Go垃圾回收(GC)整体流程

Go的GC是并发标记、并发清扫:

世界停止很短

GC是三色表记法

并发+增量+自适应(根据GOGC调整)

7、GC完整流程图

┌────────────┐
│  Root Scan │  ← STW(短暂)
└─────┬──────┘
      │
      ▼
┌────────────┐
│  Marking   │  ← 并发(goroutine 与 GC 同时运行)
├────────────┤
│ Write Barrier(写屏障) │
└─────┬──────┘
      │
      ▼
┌────────────┐
│  Mark Done │ ← STW(短暂)
└─────┬──────┘
      │
      ▼
┌────────────┐
│  Sweep     │ ← 并发
└────────────┘

8、三色标记法(Tri-Color Marking)

三种颜色的含义:

颜色含义
白色未访问(可能是垃圾)
灰色已访问,但未扫描其子对象
黑色已访问,且已扫描子对象

GC从Roots开始扫描(栈、全局变量、寄存器),步骤:

8.1 初始化:所有对象都是白色

8.2 将Root对象标记为灰色,进入灰色队列

8.3 循环处理灰色对象队列

对每个灰色对象:把它的子对象标记为灰色,自己变为黑色

8.4 结束时:黑色存活,白色将在Sweep阶段被回收

9、写屏障(Write Barrier):并发GC保证正确性的关键

并发GC时用户程序还在运行,会出现:新引用出现或者白对象被指向,导致对象遗漏,Go使用混合写屏障来保证三色不变性。

写屏障规则:

在程序写指针(p=q)时:将新引用的对象标记为灰色,将旧引用的对象标记为灰色(必要时)

核心保证:黑对象永远不指向白对象

10、Sweep阶段:并发清除白色对象,这个阶段和程序并发运行,不会产生STW(Stop the word)

11、GOGC:GC调度器

GOGC默认是100,表示:当本来GC后heap增长100%,再次触发GC。

GOGC=50   // 更频繁 GC
GOGC=200  // 更少 GC
GOGC=off  // 关闭自动 GC

12、从GC视角出发,如何写出更高效的代码?

  • 尽量避免逃逸(减少堆对象)

  • 减少大对象创建(>32KB)

  • 重用对象(sync.Pool)

  • 避免短命且大量分配的临时 slice/map

  • 使用 [][]byte 而非 string 大规模拼接

  • 用 make 预估容量,避免扩容

*源码地址*

1、公众号“Codee君”回复“每日一Go”获取源码

2、源码获取链接: pan.baidu.com/s/1B6pgLWfS… 提取码: jc1s


如果您喜欢这篇文章,请点赞、推荐+分享给更多朋友,万分感谢!