从Go语言角度浅谈内存分配

1. 前言

为了更好的理解Go语言是如何进行内存分配的,本文先介绍一下内存的相关知识。计算机内存在逻辑上可以分为堆(heap)和栈(stack)两部分。堆是由操作系统分配的内存空间,由程序员自行管理;栈则是编译器自动分配和管理的,用于存放局部变量、函数参数等。

在堆中,由程序员自己使用mallocnewmake等函数来进行内存的分配和释放;而在栈中,内存的分配与释放是由编译器完成的,不需要程序员手动干预。由于栈的分配和释放速度非常快,因此一般建议在函数中使用局部变量时,优先选择在栈中分配内存。

2. Go语言内存分配

2.1 Go语言内存结构

Go语言将内存分为多个不同的部分,每个部分有不同的用途。其中最主要的两个部分是堆(heap)和栈(stack),另外还有的是静态区、常量区等。以下是Go语言内存结构的示意图:

+-------------------------+ <- 0xFFFFFFFFFFFFFFFF (64-bit)

| stack |

+-------------------------+ <- 栈顶

| ... |

+-------------------------+

| stack |

+-------------------------+

| |

| heap |

| |

+-------------------------+ <- 堆顶

| static area |

+-------------------------+

| read-only area |

+-------------------------+

| zero-value area |

+-------------------------+ <- 0x0000000000000000

栈和堆都是内存中的一段区域,栈是向下生长的,也就是说,随着栈帧入栈,栈的地址会变小;堆是向上生长的,随着内存分配和释放,堆的地址会变大。

2.2 Go语言内存分配器

Go语言的内存分配器使用了一个称为mcache的对象来管理内存。每个Go程(goroutine)都有自己的mcache对象,用来缓存内存分配和释放的操作,以减少对全局内存分配器的访问次数,提高程序的运行效率。下面是mcache的数据结构示意图:

+------------+---+

| cachebase |--+|

| | | +-------> empty list

+-----------+ |

| | +-------> non-empty list

+-----------+ |

| | | +-------> span

| | |

| | +-------> span.next

+------------+-- |

| | |

| | |

| | |

| | |

+------------+---+ <- 页

cachebase字段指向一个缓存区域,缓存区中包含了已经被分配给Go程的内存。其中,第一个地址被称为页(page),每个页占用mcache结构体中的64字节空间。页是由span数据结构构成的,每个span包含了一块连续的内存区域,并保存了这块内存区域的相关信息,如内存大小、使用情况等。

当Go程需要分配内存时,会先检查cachebase中是否有可用的内存。如果有,则直接从缓存区中分配内存,并返回相应的指针;如果没有,则需要向全局内存分配器请求一块新的内存区域。内存分配器会查找是否有合适大小的空闲区域,如果有,则返回该内存区地址;如果没有,则重新映射新的虚拟地址,以扩大堆的大小。

2.3 内存池

为了避免频繁地向全局内存分配器请求内存,在内存分配的过程中,Go语言引入了内存池(memory pool)的概念。内存池是一种由预先分配一定数量的内存块而组成的“缓冲池”,在程序启动时被创建,一般不会销毁,用于提高内存分配的效率。

在Go语言中,内存池被应用到了常见的对象内存分配过程中,例如channel、slice等。当程序需要分配这些对象时,会先从内存池中查找是否有可用的内存块,如果有,则直接使用;如果没有,则向全局内存分配器请求一块新的内存区域,并将该内存块加入内存池,以备后续使用。

3. 内存安全

在Go语言中,为了保证内存的安全性,引入了指针的概念。指针(Pointer)是一个变量,它存储了另一个变量的地址。Go语言中使用指针访问内存时,同样需要遵守以下几个规则:

1. 禁止将指针指向没有被分配的内存地址;

2. 禁止使用已经释放的内存;

3. 禁止访问超出边界的内存区域。

Go语言通过强制使用指针和边界检查等措施,来保护程序不被非法访问内存。这些措施可以有效地提高程序的安全性和健壮性。

4. 总结

本文从Go语言角度浅谈了内存分配的相关知识,包括内存的相关结构、内存分配器、内存池以及内存安全等方面的内容。在深入理解这些知识的基础上,可以更好地掌握Go语言的内存管理机制,从而写出更加优化和高效的程序。

后端开发标签