前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >go源码剖析2 内存分配1 概述

go源码剖析2 内存分配1 概述

原创
作者头像
历久尝新
修改2020-05-19 17:55:15
4900
修改2020-05-19 17:55:15
举报
文章被收录于专栏:学而时习之

内存分配的基本策略:

  1. 每次从操作系统中分配一块大的内存(eg 1mb), 以减少系统调用;
  2. 将申请到的大块内存按照特定大小预先切分成小块, 构成链表;
  3. 为对象分配内存时, 只需要从大小合适的链表提取一小块即可;
  4. 回收对象内存时, 将该小块内存重新归还到原链表, 方便使用;
  5. 如闲置内存过低, 则尝试归还部分内存给操作系统;

注意: 内存分配器只管理内存块, 不关心内存中对象的状态, 也不会主动的回收内存, 回收是在回收器完成清理操作后, 触发内存分配器的回收操组;

内存块

  • span: 由多个连续的page组成的大块内存;
  • object: 将span按照特定大小切分成多个小块, 每个小块可存储一个对象

span面向内部管理, object面向对象分配

关于span:

  • 分配器按页数来区分不同大小的span, 以页数为单位将span存放到内存管理数组中, 需要时就以页数为索引进行查找;
  • span的大小并非固定不变, 在获取闲置span时, 如果没有找到大小合适的, 此时会引发裁剪操作, 将多余部分将构成新的span被放回管理数组;
  • 分配器会尝试将地址相邻的空闲span合并, 构建更大的内存快, 减少碎片, 提供更灵活的分配策略;

malloc.go

代码语言:javascript
复制
	pageShift = _PageShift
	_PageSize = 1 << _PageShift // 8kb

mheap.go

代码语言:javascript
复制
type mspan struct {
	next	 *mspan     // 双向链表
	prev 	 *mspan     
	start	 pageID		// 起始序号 = (address >> _pageshift)
	npages 	 uintptr	// 页数
	freelist gclinkptr  // 带分配的object 链表 
	...
	}	

关于object:

  • 用于储存对象的object, 按照8字节倍数分配N种, eg 大小为24的object 的存储范围在17-24字节的对象;
  • 虽然会造成一些内存浪费, 但是分配器只用面对有限几种规格的小内存, 优化了分配和服用的管理策略;
  • 分配器会尝试将多个微小的对象组合到一个object中, 以节约内存;

malloc.go

代码语言:javascript
复制
_MaxSamllSize = 32 << 10 //32kb
  • 若对象大小超过特定yuzhi限制, 会被当做大对象特别对待;

管理组件:

使用tcmalloc架构

优秀的内存分配器必须要在性能和内存利用率之间做到平衡. go的起点很高, 直接采用了tcmalloc架构

malloc.go

代码语言:javascript
复制
// Memory allocator.
//
// This was originally based on tcmalloc, but has diverged quite a bit.
// http://goog-perftools.sourceforge.net/doc/tcmalloc.html

分配器由三种组件组成.

  • cache:每个运行期工作线程都会绑定一个cache, 用于无锁object分配
  • central:为所有cache提供切分好的span资源
  • heap: 管理闲置span 需要时向操作向申请内存

mheap.go

代码语言:javascript
复制
type mheap strcut {
    free       [_MaxMheapList]mspan // 页数在127以内的闲置span链表数组
    freelarge  mspan                // 页数大于127(>=1MB)的大span链表
    // 每个central 对应一种 sizeclass
    central    [_NumSizeClass]struct{
                mcentral mcentral
    }
}

mcentral.go

代码语言:javascript
复制
typt mcentral strcut {
    sizeclass int32 // 规格
    nonempty  mspan // 链表, 尚有空闲的object 的span
    empty     mspan // 链表, 没有空闲的object, 或已被cache取走的span
}

mcache.go

代码语言:javascript
复制
type mcache struct {
    alloc [_NumSizeClasses]*mspan // 以sizeclass为索引管理多个用于分配的span
}

分配流程:

  1. 计算待分配对象对应的规格(size class)
  2. 从cache.alloc 数组找到规格相同的span.
  3. 从span.freelist 链表提取可用的object.
  4. if span.freelist 为空, 则去central获取新的span.
  5. if central.nonempty 为空, 从heap.free/freelarge获取, 并切分成object链表.
  6. if heap 没有大小合适的闲置span. 向操作系统申请新内存块.

# cache.alloc --> span.freelist --> central --> central.nonempty --> heap.free/freelarge

释放流程:

  1. 将标记为可回收的object交还给所属的span.freelsit
  2. 该span被放回central, 可提供cache获取新的span
  3. if span 中的object全部被回收, 则将其交还给heap. 一遍重新切分复用.
  4. 定期扫描heap中长时间闲置的span. 释放其占用的内存.

注意: 分配和回收不包括大对象, 他直接从heap分配和回收

小结

cache 是工作线程私有且不被共享, 是实现高性能无锁分配的核心.

central 的作用是在多个cache间提高object的利用率.避免内存浪费.

假设cache1 获取一个span后, 仅仅使用了一部分object, 那么剩余的空间就可能会被浪费, 而回收操作将该span交换给central后, 该span完全可以被cache2 cache3 ... 获取使用, 此时 cache1 不再持有该span, 完全不会造成问题

将span归还给heap, 是为了在不同规格object需求间平衡

某段时间内某种规格的object需求可能激增, 当需求过后, 大量的被切分成该规格的span就会闲置浪费, 将其归还给heap, 就可以被其他需求获取, 重新切分

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 内存分配的基本策略:
  • 内存块
    • 关于span:
      • 关于object:
      • 管理组件:
        • 使用tcmalloc架构
          • 分配器由三种组件组成.
            • 分配流程:
              • 释放流程:
                • 小结
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档