go内存分配
2021-09-09 15:53:12 0 举报
AI智能生成
go内存管理详细介绍
作者其他创作
大纲/内容
是一种高效的内存分配方式,只需要在内存中维护一个指向内存特定位置的指针,当用户程序需要申请内存时修改指针的位置
定义
较低的实现复杂度
执行速度快
优点
对于已经释放的内存无法重写使用
缺点
线性分配器具有上述特性,所以需要与合适的垃圾回收算法配合使用,例如:标记压缩(Mark-Compact)、复制回收(Copying GC)和分代回收(Generational GC)等算法,它们可以通过拷贝的方式整理存活对象的碎片,将空闲内存定期合并,这样就能利用线性分配器的效率提升内存分配器的性能了
使用
线性分配器
是一个链表,当用户需要内存的时候就便利空闲的内存块,找到一个够大的内存,就分配并修改链表
有利于内存的回收利用
每次分配内存的时候都需要遍历链表,时间复杂度是O(n)
首次适应(First-Fit)— 从链表头开始遍历,选择第一个大小大于申请内存的内存块
循环首次适应(Next-Fit)— 从上次遍历的结束位置开始遍历,选择第一个大小大于申请内存的内存块
最优适应(Best-Fit)— 从链表头遍历整个链表,选择最合适的内存块;
隔离适应(Segregated-Fit)— 将内存分割成多个链表,每个链表中的内存块大小相同,申请内存时先找到满足条件的链表,再从链表中选择合适的内存块
分配策略
空闲链表分配器
分配方法
Go语言的内存分配器就借鉴了 TCMalloc(Thread-Caching Malloc) 的设计实现高速的内存分配,它的核心理念是使用多级缓存将对象根据大小分类,并按照类别实施不同的分配策略。
对象大小
线程缓存(Thread Cache)
中心缓存(Central Cache)
页堆(Page Heap)
多级缓存
go的分级分配
设计思想: 建立在堆区的内存时连续的这一假设上,启动时初始化整片虚拟内存区域,分为三个区域spans、bitmap 和 arena 分别预留了 512MB、16GB 以及 512GB 的内存空间
spans 区域存储了指向内存管理单元 runtime.mspan 的指针,每个内存单元会管理几页的内存空间,每页大小为 8KB;(512GB/8KB*8 byte= 512M)
bitmap 每个指针大小的内存都会有两个bit分别表示是否应该继续扫描和是否包含指针; (512GB/(8*8/2 )=16GB)
arena 区域是真正的堆区,运行时会将 8KB 看做一页,这些内存页中存储了所有在堆上初始化的对象;
内部实现
设计简单,使用方便
分配的内存地址会发生冲突,导致堆的初始化和扩容失败
没有被预留的大块内存可能会被分配给 C 语言的二进制,导致扩容后的堆不连续
内存上限是512GB
线性内存<=1.10
将原有的连续大内存切分成稀疏的小内存,而用于管理这些内存的元信息也被切分成了小块
设计思想
各平台二维数组情况
运行时使用二维的 runtime.heapArena 数组管理所有的内存,每个单元都会管理 64MB 的内存空间
大约会增加 1% 的垃圾回收开销
内存的管理变得更加复杂
稀疏内存 >1.10
虚拟内存布局
设计原理
Go 语言内存管理的基本单元
mspan之间形成双向链表结构,每个mspan 都管理 n 个大小为 8KB 的页
每个mspan都有一个spanClass,代表是了那种跨度类型的span
startAddr 和 npages — 确定该结构体管理的多个页所在的内存,每个页的大小都是 8KB;
freeindex — 扫描页中空闲对象的初始索引;
allocBits 和 gcmarkBits — 分别用于标记内存的占用和回收情况;
allocCache — allocBits 的补码,可以用于快速查找内存中未被使用的内存
结构
mspan
是 Go 语言中的线程缓存,它会与线程上的处理器一一绑定,主要用来缓存用户程序申请的微小对象。每一个线程缓存都持有 68 * 2 个 runtime.mspan,这些内存管理单元都存储在结构体的 alloc 字段中
初始化时是不包含 mspan ,只有当用户程序申请内存时才会从上一级组件获取新的 mspan 满足内存分配的需求。
初始化
当 mcache 中没有找到可用指定跨度的 mspan ,会从 mcentral 中的可用 mspan 将其替换
替换
tiny: 指向堆中的一片内存,tinyOffset
tinyOffset: 是下一个空闲内存所在的偏移量
local_tinyallocs: 会记录内存分配器中分配的对象个数
组成
默认管理16B以下的对象
运行时将小于 16 字节的对象划分为tiny对象,它会使用线程缓存上的tiny分配器提高微对象分配的性能,我们主要使用它来分配较小的字符串以及逃逸的临时变量。tiny分配器可以将多个较小的内存分配请求合入同一个内存块中,只有当内存块中的所有对象都需要被回收时,整片内存才可能被回收.
使用场景
tiny分配器
为什么这里需要分scan跟noscan?
思考
mcache
1. 每个 mcentral 都是一种跨度类型的 mspan的全局后备资源,因此共有 136 个 mcentral;2. 访问中心缓存中的内存管理单元需要使用互斥锁
spanclass: 代表这个mcentral是那种类型的mspan
partial: 代表里面有空闲对象的mspan
full: 代表了没有空闲对象的mspan
调用 runtime.mcentral.partialSwept从清理过的、partial中查找可以使用的msapn;调用 runtime.mcentral.partialUnswept从未被清理过的、partial中查找可以使用的内存管理单元调用 runtime.mcentral.fullUnswept 获取未被清理的、full中获取内存管理单元并通过 runtime.mspan.sweep 清理它的内存空间调用 runtime.mcentral.grow 从堆中申请新的内存管理单元;更新内存管理单元的 allocCache 等字段帮助快速分配内存;
内存分配过程
mcentral
runtime.mheap 是内存分配的核心结构体,Go 语言程序会将其作为全局变量存储,而堆上初始化的所有对象都由该结构体统一管理,该结构体中包含两组非常重要的字段,其中一个是全局的中心缓存列表 central,另一个是管理堆区内存区域的 arenas 以及相关字段
mheap
内存管理组件
memory
Demystifying memory management in modern programming languages
go memory
Visualizing memory management in Golang
go
A visual guide to Go Memory Allocator from scratch (Golang)
golang内存分配
https://www.php.cn/be/go/436885.html
引用
Memory Allocator
0 条评论
回复 删除
下一页