内存

65 阅读2分钟

声明变量后,会在内存分配一个空间,空间中究竟发生了什么呢?以进程视角内存布局为例解释

c7e1e19407cbf1fc3f49e40a258a388.jpg

高低地址是内存地址数值的大小,栈区向下生长数值越小,堆区向上生长数值越大。自下而上,从两部分解释上图

一、通用进程内存区域

这些区域是操作系统对进程内存的标准化划分,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)

  • 存储内容:用于动态分配的对象(如通过 newmake 创建的切片、映射、通道,或逃逸到堆上的变量)
  • 特点:
    • 全局共享:所有 goroutine 共享同一个堆
    • 分层管理:Go 采用 “MCache → MCentral → MHeap” 三级架构管理堆内存,避免锁竞争,提升并发分配效率

3. 命令行参数与环境变量区

  • 存储内容:程序启动时的命令行参数(如 go run main.go arg1 arg2 中的 arg1)和环境变量(如 PATHGOPATH
  • 特点:位于内存高地址区域,由操作系统在程序启动时加载