Skip to main content

内存分区

零 前置知识

操作系统的每个进程都认为自己可以访问计算机的所有物理内存,但由于计算机必定运行着多个程序,每个进程都不能拥有全部内存。

为了避免了进程直接访问实际的物理地址,操作系统会将物理内存虚拟为一个数组,每个元素都有一个唯一的物理地址(PA)。 物理存储其器中存储着一个页表(page table),该表即虚拟地址与物理地址的映射表,读取该也表,即可完成地址翻译。

假设一个程序访问地址为0x1001的内存,实际上,该数据并不一定是存储在0x1001的物理地址中,甚至也不在物理内存中(如果物理内存满了,则可以转移到磁盘上)。这些地址不必反映真实的物理地址,可以称为“虚拟内存”。

一 内存分区

1.0 程序的内存使用

现在使用命令来查看Go程序的内存使用:

go build main.go
size main

此时会显示Go程序在未启动时,内存的使用情况:

![] (runtime-01.png)

此时可执行程序内部已经分好了三段信息,分别为:

  • text 代码区
  • data 数据区
  • bss 未初始化数据区

贴士: data和bss区域可以一起称呼为静态区/全局区

  • 上述三个区域大小都是固定的

程序在执行后,会额外增加栈区、堆区。

1.1 text 代码区

代码区用于存放CPU执行的机器指令,一般情况下,代码区具备以下特性:

  • 共享:即可以提供给其他程序调用,这样就可以让代码区的数据在内存中只存放一份即可,有效节省空间。
  • 只读:用于放置程序修改其指令
  • 规划局部变量信息

1.2 data 数据区

数据区用于存储数据:

  • 被初始化后的全局变量
  • 被初始化后的静态变量(包含全局静态变量、局部静态变量)
  • 常量数据(如字符串常量)

1.3 bss 未初始化数据区

未初始化数据区用于存储:

  • 全局未初始化变量
  • 未初始化静态变量

如果是C语言,未初始化,却被使用了,这会产生一个随机的值。Go语言中,为了防止C的这种现象,该区域的数据会在程序执行之前被初始化为零值(0或者空)。

1.4 stack 栈区

栈是一种先进后出(FILO)的内存结构,由编译器自动进行分配和释放。一般用于存储:函数的参数值、返回值、局部变量等。栈区大小一般只有1M,也可以实现扩充:

  • Windows最大可以扩充为10M
  • Linux最大可以扩充为16M

1.5 heap 堆区

栈的内存空间非常小,当我们遇到一些大文件读取时,栈区是不够存储的,这时候就会用到堆区。堆区空间比较大,其大小与计算机硬件的内存大小有关。

堆区没有栈的先进后出的规则,位于BSS区域栈区之间,用于内存的动态分配。

在C、C++等语言中,该部分内存由程序员手动分配(c中的malloc函数,c++中的new函数)和释放(C中的free函数,C++的delete函数),如果不释放,可能会造成内存泄露,但是程序结束时,操作系统会进行回收。

在Java、Go、JavaScript中,都有垃圾回收机制(GC),可以实现内存的自动释放!

注意:Go语言与其他语言不同,对栈区、堆区进行虚拟管理。

1.6 操作系统内存分配图

操作系统会为每个进程分配一定的内存地址空间,如图所示:

![] (runtime-02.svg)

上图所示的是32位系统中虚拟内存的分配方式,不同系统分配的虚拟内存是不同的,但是其数据所占区域的比例是相同的:

  • 32位:最大内存地址为232,这么多的字节数换算为G单位,即为4G。(换算为1G=1024MB=10241024KB=10241024*1024B)
  • 64位:最大内存地址为264,这么多的字节数换算为G单位,数值过大,不便图示

注意:栈区是从高地址往低地址存储的