
文末有源码下载链接!
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、小对象和大对象的分配策略
|---------|-----------------|
| 对象大小 | 分配方式 |
| <=32KB | mcache中的小对象快速分配 |
| >32KB | 大对象,直接在堆heap上分配 |
4、栈内存和堆内存
Go使用可憎长栈(由2KB~8GB):
栈内存快、无需GC,函数返回自动销毁
堆内存慢,需要GC管理
因此,尽可能把对象放在栈内存上,更高效,要做到这一点,依赖逃逸分析。
5、逃逸(Escape)分析
什么是逃逸?编译器决定变量放在栈上还是放在堆上,放在堆上的就产生了逃逸。可以用以下命令来查看自己的程序哪些地方产生了逃逸:
bash
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完整流程图
bash
┌────────────┐
│ 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。
bash
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
如果您喜欢这篇文章,请点赞、推荐+分享给更多朋友,万分感谢!