golang源码解析--内存总览

看了gc,发现没有内存的知识,光看gc,只能背个流程,其中很多涉及内存的操作,所以先来了解一波内存,然后了解gc的协程,golang的锁,再回头看gc

golang内存分配的简介

关于golang的内存分配

原理:
思想来源于Thread-CachingMalloc。核心思想就是把内存分为多级管理,从而降低锁的粒度。它将可用的堆内存采用二级分配的方式进行管理:每个线程都会自行维护一个独立的内存池,进行内存分配时优先从该内存池中分配,当内存池不足时才会向全局内存池申请,以避免不同线程对全局内存池的频繁竞争。

内存分配工作通过内存页来进行。
小的内存分配(0~32kb)被分成了70个级别,每一个都有它自己的一组完全相同大小的对象。任何可用内存页都可以拆分为一组对象,然后使用可用位图管理这些对象。

相关的数据结构

fixalloc:固定大小堆外对象的空闲列表分配器,用于管理分配器使用的存储。
mheap:malloc堆,按页(8192字节)粒度管理。
mspan:由mheap管理的一系列页面。
mcentral:收集给定大小类的所有span
mcache: 一个P的可用内存空间
mstats: 分配统计信息

golang如何分配一个小对象

分配一个小对象将沿着缓存的层次结构前进
1.将小对象的大小,四舍五入的(保证class的大小大于对象内存)分配在一个小类中,并在P的mcache中查询对应的mspan,扫描mcache中的mspan的空余位去找可用的slot。如果存在可用slot,分配内存。这个过程的完成不需要锁。
2.如果申请的mspan在mcache中没有可用插槽,请从mcentral的具有可用空间的所需大小类别的mspan列表中获取一个新的mspan。获得整个span会摊销锁定mcentral的成本。
3.如果mcentral的mspan列表为空,请从mheap获取一系列用于mspan的页面。
4.若mheap为空,或者没有足够大的页,从操作系统申请一组新的页(至少1MB),分配大量的页面可以分摊与操作系统对话的成本。

golang如何释放mspan上的内存

扫掠mspan并释放其上的对象将沿着类似的层次结构进行:
1.如果mspan是响应于分配而被回收的,那么它将被返回到mcache以满足分配。
2.非情况1,如果msapn中仍有分配的对象,则它被放在mcentral的mspan的size类的空余列表中
3.非1,2,如果所有的对象在mspan中都是空闲的,则mspan是空闲的,则会返回mheap并不再有size class的属性。这可能会与相邻的空闲mspan合并。
4.如果mspan保持空闲状态足够长的时间,则将其页面返回到操作系统。

golang如何分配和释放一个大对象

分配和释放大对象直接使用mheap,而绕过mcache和mcentral。

延迟归零机制

仅当mspan.needzero为false时,才会将mspan中的可用对象插槽清零。
如果needzero为true,则在分配对象时将其清零。
延迟调零有多种好处:
1.堆栈帧分配可以完全避免归零。
2.由于程序可能即将写入内存,因此它展现了更好的时间局部性。
3.我们不会将永远不会被重用的页面归零。

虚拟内存层

堆由一组arena组成,在64位计算机上是64MB,在32位计算机上是4MB,每个arena的起始地址也与arena大小对齐
每个arena都有一个关联的heapArena对象,该对象存储该arena的元数据:arena中所有单词的堆位图和arena中所有页面的跨度图。 它们本身是堆外分配的。
由于arena是对齐的,因此可以将地址空间视为一系列arena框架。arena映射(mheap_.arenas)从arena帧号映射到* heapArena,对于不由Go堆支持的部分地址空间,映射为nil。 arena图(map)的结构为两层数组,由“ L1”arena图和许多“ L2”arena图组成。 但是,由于arena很大,因此在许多体系结构上,arena map都由一个单独的大型L2 map组成。
arena map覆盖了整个可能的地址空间,从而允许Go堆使用地址空间的任何部分。 分配器尝试使arena保持连续,以便大跨度(以及大对象)可以跨越arena。

golang关于内存分配的基础概念

这一部分内容源自http://www.sohu.com/a/300983903_657921
在这里插入图片描述
arena区域就是我们所谓的堆区,Go动态分配的内存都是在这个区域,它把内存分割成 8KB大小的页,一些页组合起来称为 mspan。
bitmap区域标识 arena区域哪些地址保存了对象,并用 4bit标志位表示对象是否包含指针、 GC标记信息。 bitmap中一个 byte大小的内存对应 arena区域中4个指针大小(指针大小为 8B )的内存,所以 bitmap区域的大小是 512GB/(4x8B)=16GB。
spans区域存放 mspan(也就是一些 arena分割的页组合起来的内存管理基本单元,后文会再讲)的指针,每个指针对应一页,所以 spans区域的大小就是 512GB/8KBx8B=512MB。除以8KB是计算 arena区域的页数,而最后乘以8是计算 spans区域所有指针的大小。创建 mspan的时候,按页填充对应的 spans区域,在回收 object时,根据地址很容易就能找到它所属的 mspan。
总结
Go在程序启动时,会向操作系统申请一大块内存,之后自行管理。
Go内存管理的基本单元是mspan,它由若干个页组成,每种mspan可以分配特定大小的object。
mcache, mcentral, mheap是Go内存管理的三大组件,层层递进。mcache管理线程在本地缓存的mspan;mcentral管理全局的mspan供所有线程使用;mheap管理Go的所有动态分配内存。
极小对象会分配在一个object中,以节省资源,使用tiny分配器分配内存;一般小对象通过mspan分配内存;大对象则直接由mheap分配内存。
概念:
mspan:Go中内存管理的基本单元,是由一片连续的 8KB的页组成的大块内存。注意,这里的页和操作系统本身的页并不是一回事,它一般是操作系统页大小的几倍。一句话概括: mspan是一个包含起始地址、 mspan规格、页的数量等内容的双端链表。
内存管理组件

内存分配由内存分配器完成。分配器由3种组件构成: mcache, mcentral, mheap。
mcache

mcache:每个工作线程都会绑定一个mcache,本地缓存可用的 mspan资源,这样就可以直接给Goroutine分配,因为不存在多个Goroutine竞争的情况,所以不会消耗锁资源。
mcentral:为所有 mcache提供切分好的 mspan资源。每个 central保存一种特定大小的全局 mspan列表,包括已分配出去的和未分配出去的。 每个 mcentral对应一种 mspan,而 mspan的种类导致它分割的 object大小不同。当工作线程的 mcache中没有合适(也就是特定大小的)的 mspan时就会从 mcentral获取。
mheap:代表Go程序持有的所有堆空间,Go程序使用一个 mheap的全局对象 _mheap来管理堆内存。
当 mcentral没有空闲的 mspan时,会向 mheap申请。而 mheap没有资源时,会向操作系统申请新内存。 mheap主要用于大对象的内存分配,以及管理未切割的 mspan,用于给 mcentral切割成小对象。
同时我们也看到, mheap中含有所有规格的 mcentral,所以,当一个 mcache从 mcentral申请 mspan时,只需要在独立的 mcentral中使用锁,并不会影响申请其他规格的 mspan。

源码分析

golang内存分配的过程

mallocgc

//分配一个大小为字节的对象。
//从per-P缓存的空闲列表中分配小对象。
//从堆直接分配大对象(> 32 kB)。
func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
   
	//目前gc还处于stop the world阶段,禁止申请
	if gcphase == _GCmarktermination {
   
		throw("mallocgc called with gcphase == _GCmarktermination")
	}
	//分配空指针
	if size == 0 {
   
		return unsafe.Pointer(&zerobase)
	
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值