声明变量后,会在内存分配一个空间,空间中究竟发生了什么呢?以进程视角内存布局为例解释
高低地址是内存地址数值的大小,栈区向下生长数值越小,堆区向上生长数值越大。自下而上,从两部分解释上图
一、通用进程内存区域
这些区域是操作系统对进程内存的标准化划分,Go 程序也遵循此框架:
1. Text(代码段)
- 存储内容:程序的可执行指令(编译后的机器码)
- 特点:只读(防止指令被意外修改),可被多个进程共享(如多个 Go 进程运行同一份程序时,代码段可共享以节省内存)
2. Initialized Data(初始化数据段)
- 存储内容:已初始化的全局变量、静态变量(包括有初始值的全局变量、静态全局变量、静态局部变量)
- 特点:变量的初始值在编译时确定,由操作系统在程序启动时从可执行文件中加载。例如:
var globalInit = 10 // 存放在 initialized data 段
3. Uninitialized Data (BSS)(未初始化数据段)
- 存储内容:未初始化的全局变量、静态变量,这些变量会被操作系统自动初始化为 0
- 特点:可执行文件中只需记录该区域的大小(无需存储具体值),从而节省文件体积。例如:
var globalUninit int // 存放在 bss 段
二、Go 语言特有
Go 为支持高并发(协程)和自动垃圾回收,在通用进程内存区域上新增了协程栈和全局堆管理的设计:
1. Goroutine 栈(协程栈)
- 存储内容:每个 goroutine 专属的局部变量、函数参数、返回地址等函数调用上下文。
- 特点:
- 动态伸缩:初始大小仅 2KB,可根据需要自动增长
- 私有权限:每个 goroutine 独立拥有一个栈,不与其他 goroutine 共享,保证并发安全
- 栈上分配偏好:Go 编译器通过逃逸分析,会优先将生命周期局限于函数内的变量分配到栈上(而非堆),以减少垃圾回收压力。例如:
func stackDemo() {
var localVar = 100 // 未逃逸,分配在 goroutine 栈上
}
2. 全局堆(Heap)
- 存储内容:用于动态分配的对象(如通过
new、make创建的切片、映射、通道,或逃逸到堆上的变量) - 特点:
- 全局共享:所有 goroutine 共享同一个堆
- 分层管理:Go 采用 “MCache → MCentral → MHeap” 三级架构管理堆内存,避免锁竞争,提升并发分配效率
3. 命令行参数与环境变量区
- 存储内容:程序启动时的命令行参数(如
go run main.go arg1 arg2中的arg1)和环境变量(如PATH、GOPATH) - 特点:位于内存高地址区域,由操作系统在程序启动时加载