Go语言取整 go语言struct

golang是自动释放内存吗golang是一门自带垃圾回收的语言 , 它的内存分配器和tmalloc(thread-caching malloc)很像,大多数情况下是不需要用户自己管理内存的 。最近了解了一下golang内存管理,写出来分享一下 , 不正确的地方请大佬们指出 。
1.内存池:
应该有一个主要管理内存分配的部分,向系统申请大块内存 , 然后进行管理和分配 。
2.垃圾回收:
当分配的内存使用完之后,不直接归还给系统,而是归还给内存池,方便进行下一次复用 。至于垃圾回收选择标记回收,还是分代回收算法应该符合语言设计初衷吧 。
3.大小切分:
使用单独的数组或者链表,把需要申请的内存大小向上取整,直接从这个数组或链表拿出对应的大小内存块,方便分配内存 。大的对象以页申请内存,小的对象以块来申请 , 避免内存碎片,提高内存使用率 。
4.多线程管理:
每个线程应该有自己的内存块,这样避免同时访问共享区的时候加锁,提升语言的并发性,线程之间通信使用消息队列的形式,一定不要使用共享内存的方式 。提供全局性的分配链,如果线程内存不够用了,可向分配链申请内存 。
这样的内存分配设计涵盖了大部分语言的,上面的想法其实是把golang语言内存分配抽象出来 。其实Java语言也可以以同样的方式理解 。内存池就是JVM堆,主要负责申请大块内存;多线程管理方面是使用栈内存 , 每个线程有自己独立的栈内存进行管理 。
golang内存分配器
golang内存分配器主要包含三个数据结构:MHeap,MCentral以及MCache
1.MHeap:分配堆 , 主要是负责向系统申请大块的内存,为下层MCentral和MCache提供内存服务 。他管理的基本单位是MSpan(若干连续内存页的数据结构)
type MSpan struct
{
MSpan*next;
MSpan*prev;
PageIdstart;// 开始的页号
uintptrnpages; // 页数
…..
};
可以看出MSpan是一个双端链表的形式,里面存储了它的一些位置信息 。
通过一个基地址+(页号*页大小),就可以定位到这个MSpan的实际内存空间 。
type MHeap struct
{
lockmutex;
free[_MaxMHeapList] mSpanList// free lists of given length
freelarge mSpanList// free lists length = _MaxMHeapList
busy[_MaxMHeapList] mSpanList// busy lists of large objects of given length
busylarge mSpanList
};
free数组以span为序号管理多个链表 。当central需要时,只需从free找到页数合适的链表 。large链表用于保存所有超出free和busy页数限制的MSpan 。
MHeap示意图:
2.MCache:运行时分配池,不针对全局,而是每个线程都有自己的局部内存缓存MCache,他是实现goroutine高并发的重要因素,因为分配小对象可直接从MCache中分配 , 不用加锁,提升了并发效率 。
type MCache struct
{
tinybyte*;// Allocator cache for tiny objects w/o pointers.
【Go语言取整 go语言struct】tinysizeuintptr;
alloc[NumSizeClasses] MSpan*;// spans to allocate from
};
尽可能将微小对象组合到一个tiny块中,提高性能 。
alloc[]用于分配对象,如果没有了,则可以向对应的MCentral获取新的Span进行操作 。
线程中分配小对象(16~32K)的过程:
对于
size 介于 16 ~ 32K byte 的内存分配先计算应该分配的 sizeclass,然后去 mcache 里面
alloc[sizeclass] 申请,如果 mcache.alloc[sizeclass] 不足以申请,则 mcache 向 mcentral
申请mcentral 给 mcache 分配完之后会判断自己需不需要扩充,如果需要则想 mheap 申请 。
每个线程内申请内存是逐级向上的,首先看MCache是否有足够空间,没有就像MCentral申请 , 再没有就像MHeap,MHeap向系统申请内存空间 。

推荐阅读