一、Linux系统内存在讲解golang内存分配之前,先了解一下Linux系统内存的基础知识,有助于理解golang内存分配的原理。1.1虚拟内存技术在早期的内存管理中,如果程序太大,超过了空闲内存的容量,就没有办法将所有的程序加载到内存中,这样的解决方案。这是什么样的解决方案?就是把程序分成几个部分,叫做叠加,核心思想是分解(类似于现代建筑技术中的分解和子模块的思想)。那么只有那些需要用到的指令和数据存放在内存中,其余的指令和数据存放在内存外。关键是需要程序员手动分块。这项技术有什么问题?该技术必须由程序员手工将一个大程序分成若干个小的功能模块,并确定各个模块之间的调用关系。手动做这种事情既费时又费力,增加了编程的复杂度。然而,程序员总是爱“偷懒”,于是人们寻找更好的解决方案。这种解决方案就是虚拟内存技术,它的基本思想是:程序运行进程的总大小可以超过实际可用物理内存的大小。每个进程都可以有自己独立的虚拟地址空间。然后虚拟内存地址被CPU和MMU翻译成实际的物理地址。这相当于在物理内存和程序之间加了一个中间层,虚拟内存。虚拟存储也可以看作是内存的抽象。而这种抽象带来了很多好处:它把内存看作是存储在磁盘上的地址空间的缓存,只保留内存中的活动区域,可以根据需要在磁盘和内存之间来回传输数据,并使用它高效记忆。它为每个进程提供了一致的地址空间,简化了存储管理。它保护进程不被其他进程的地址空间破坏,因为每个进程的地址空间是相互独立的。(程序:静态程序;进程:动态,可视为程序的一个实例)缺点:难免复杂度进一步增加。不过,相对于带来的好处,复杂度的增加还是可以接受和克服的。Linux中进程的处理被抽象成一个结构体task_struct,我在上一篇文章中介绍过。我们看一下进程的内存。1.2进程内存linux(32位)中进程内存的布局:来自:https://manybutfinite.com/post/anatomy-of-a-program-in-memory/最高1GB是linux内核空间,用户代码不能写,否则会触发segmentfault。接下来的3GB是进程使用的内存。Kernelspace:Linux内核空间内存Stack:进程栈空间,程序运行时使用。它向下增长,系统自动管理MemoryMappingSegment:内存映射区,通过mmap系统调用,将文件映射到进程的地址空间,或者匿名映射。Heap:堆空间。这是程序中动态分配的空间。linux下,使用malloc调用extension(使用brk/sbrk扩展内存空间),free函数释放(即减少内存空间)BSS段:包含未初始化的静态变量和全局变量Data段:代码中初始化的静态变量,globalVariableText段:代码段,进程的可执行文件2.内存管理的一些常见问题不再使用的内存释放失败-内存泄漏指向不可用的内存指针-野指针指针指向的对象已被回收是的,但指向对象的指针仍然指向已被回收的内存地址-悬挂指针分配或释放内存太快或太慢。分配的内存大小不合理,造成内存碎片。内存碎片。简单分析分配,TCMalloc内存分配器的原理和golang内存分配器的原理类似,所以理解了TCMalloc之后,golang的内存分配原理也明白了大半,但是golang也有一些变化。4.Golang内存4.1golang如何解决常见的内存问题?golang是如何解决二次内存管理中的常见问题的?针对以上1、2、3三个问题,golang采用了自动垃圾回收机制。一般情况下不使用Pointer运算(运算使用unsafe包),很少用到指针。当然,内存泄漏问题并不能完全根除,但可以解决很大一部分问题。针对以下4、5、6这三个问题,golang采用了多级缓存和预分配的方式来加速内存的分配、释放和回收,尽量减少内存碎片。详见TCMalloc内存分配简析。4.2为什么要重写一个内存分配器内核已经有一个malloc内存分配器,为什么要重写一个内存分配器?可见malloc是一个非常古老的内存分配器,但是随着时代的发展,多核多线程开始流行。为了更好的应用多线程,提高程序效率,改善内存碎片,重写了一个内存分配器。从这里对TCMalloc内存分配的简单分析,我们可以看出TCMaloc的优势,将内存分为多级,减少了锁的开销。并且每个线程的缓存将多个小对象分开,减少内存碎片。等待优化和改进。所以go内存分配也继承了这些优点。go还有一个原因,就是go也有GC,需要配合内存垃圾回收。4.3内存管理管理哪些区域?从上面的进程内存布局图可以看出,一个进程的内存分为很多不同的区域,内存管理主要管理Stack和Heap。Stack(堆栈)区主要由编译器管理。而系统管理,编程语言主要是管理Heap(堆)。而这里的进程内存指的是虚拟内存。4.4golang内存中的概念golang内存分配的基本思想来源于TCMalloc,所以go内存分配中的几个概念和TCMalloc非常相似,可以看TCMalloc中的概念。mspanmspan类似于tcmalloc中的span。它是golang内存管理中的基本单位,也是由页组成的。每页大小为8KB,与tcmalloc中span组成的默认基本内存单元页大小相同。在mspan中,按照8*2n(8b,16b,32b....)的大小,将每个mspan划分为多个object。即使名字相似,mspan中的m应该是记忆的第一个字母。mcachemcache类似于tcmalloc中的ThreadCache。ThreadCache是??每个线程的缓存。同样,mcache可以为golang中的每个Processor提供内存缓存。每个mcache的单位也是mspan。mcentralmcentral类似于tcmalloc中的CentralCache。当mcache中的空间不够时,可以向mcentral申请内存。可以理解为mcentral是mcache的一个“缓存库”,供mcaceh使用。它的内存单元也是mspan。mcentral中有两个双向链表,一个表示还有空闲的mspan可以分配,一个表示链表中的所有mspan都已经分配完毕。mheapmheap类似于tcmalloc中的PageHeap,负责大内存的分配。当mcentral内存不够时,可以申请mheap。mheap没有内存资源吗?与tcmalloc一样,它适用于OS操作系统。还有,大于32KB的内存也是直接申请到mheap上的。总结一下golang内存分配的几个相关概念,用一张图总结一下:后面我们会进一步分析golang的内存分配原理。5.参考可视化golang内存管理《操作系统的设计与实现》a-program-in-memoryLinux内核分析大文
