二、基础结构 在 Go 里用于内存管理的对象结构主要是下面几个: mheap、mspan、arenas、mcentral、mcache。...mcache、mcentral、mheap 起到了内存池的作用,会被预分配内存,当有对应大小的对象需要分配时会先到它们这一层请求。...[mheap] mcache mcache 是提供给 P 的本地内存池。...[mcache] 三、总体流程 [内存分配流程] 当要分配大于 32K 的对象时,从 mheap 分配。...当要分配的对象小于等于 32K 大于 16B 时,从 P 上的 mcache 分配,如果 mcache 没有内存,则从 mcentral 获取,如果 mcentral 也没有,则向 mheap 申请,如果
allocmcache allocmcache初始化比较简单,直接看下源码吧: func allocmcache() *mcache { lock(&mheap_.lock) c :=...(*mcache)(mheap_.cachealloc.alloc()) unlock(&mheap_.lock) for i := 0; i < _NumSizeClasses; i...1、获取当前线程的私有缓存mcache 2、跟据size计算出适合的class的id 3、从mcache的alloc[class]数组中找到可用的span 4、如果mcache没有可用的span,...则从mcentral申请一个新的span加入mcache中。...(这里大家可以注意下mcache、mcentral、mheap的层级关系) 6、从该span中获取到空闲对象地址并返回。
mcache:每个运行期的goroutine都会绑定的一个mcache(具体来讲是绑定的GMP并发模型中的P,所以可以无锁分配mspan,后续还会说到),mcache会分配goroutine运行中所需要的内存空间...mcache 为了避免多线程申请内存时不断的加锁,goroutine为每个线程分配了span内存块的缓存,这个缓存即是mcache,每个goroutine都会绑定的一个mcache,各个goroutine...注意看我们的mcache在哪儿呢?就在P上! 知道为什么没有锁竞争了吧,因为运行期间一个goroutine只能和一个P关联,而mcache就在P上,所以,不可能有锁的竞争。...我们再来看看mcache具体的结构: [mem14.png] mcache中的span链表分为两组,一组是包含指针类型的对象,另一组是不包含指针类型的对象。为什么分开呢?...这些大对象的申请是由mcache发出的,而mcache在P上,程序运行的时候往往会存在多个P,因此,这个内存申请是并发的;所以为了保证线程安全,必须有一个全局锁。
分配器由3种组件构成:mcache, mcentral, mheap。...mcache mcache:每个工作线程都会绑定一个mcache,本地缓存可用的mspan资源,这样就可以直接给Goroutine分配,因为不存在多个Goroutine竞争的情况,所以不会消耗锁资源。...mcache的结构体定义: 1//path: /usr/local/go/src/runtime/mcache.go 2 3type mcache struct { 4 alloc [numSpanClasses...mcache在初始化的时候是没有任何mspan资源的,在使用过程中会动态地从mcentral申请,之后会缓存下来。当对象小于等于32KB大小时,使用mcache的相应规格的mspan进行分配。...大体上的分配流程: 32KB 的对象,直接从mheap上分配; <=16B 的对象使用mcache的tiny分配器分配; (16B,32KB] 的对象,首先计算对象的规格大小,然后使用mcache中相应规格大小的
== null) { mCache = new ImageCache(); mCache.mContext = context; } return mCache; } public...ImageCache initConfig(ImageCacheConfig config) { mCache.mConfig = config; mCache.mDir = mCache.mConfig.mDir...; if (mCache.mDir==null || mCache.mDir.length()<=0) { mCache.mDir = Environment.getExternalStorageDirectory...mCache.mDir); if (dir.exists() !...); mCache.mMyHandler = new MyHandler((Activity)mCache.mContext); return mCache; } public void
我们从Go内存管理结构图中可以看出内存管理由mcache、mcentral、mheap组成一个三级管理结构,本质上都是对mspan的管理,三者之间没有严格的包含关系,只是用于不同的目的来共同配合管理所有...小对象和大对象分配的位置不用,大对象在mheap上分配,小对象使用mcache的tiny分配器分配。...mcache:保存的是各种大小的Span,并按Span class分类,小对象直接从mcache分配内存,它起到了缓存的作用,并且可以无锁访问 Go中是每个P拥有1个mcache,因为在Go程序中,当前最多有...GOMAXPROCS个线程在运行,所以最多需要GOMAXPROCS个mcache就可以保证各线程对mcache的无锁访问 mcentral:是所有线程共享的缓存,需要加锁访问,它按Span class对...大体上的分配流程: 32KB 的对象,直接从mheap上分配; <=16B 的对象使用mcache的tiny分配器分配; (16B,32KB] 的对象,首先计算对象的规格大小,然后使用mcache中相应规格大小的
,从MCache中获取tinySize的链表补充上这个tiny链表if v.ptr() == nil { systemstack(func() { mCache_Refill(c...如果不足够,使用mcache中的tiny class对应的span分配 如果要申请的对象是小对象大小,则使用mcache中的对应span链表分配 如果对应span链表已经没有空span了,先补充上mcache...的对应链表,再分配(mCache_Refill) 如果要申请的对象是大对象,直接去heap中获取(largeAlloc) 再仔细看代码,不管是tiny大小的对象还是小对象,他们去mcache中获取对象都是使用...mCache_Refill方法为这个对象对应的链表申请内存。...func mCache_Refill(c *mcache, sizeclass int32) *mspan { // 获取当时的goroutine _g_ := getg() //
如果找不到,上一级的组件 runtime.mcache 会为调用 runtime.mcache.refill 更新内存管理单元以满足为更多对象分配内存的需求。...mcache runtime.mcache 是 Go 的线程缓存,它会与线程上的处理器(P)一一绑定,主要用来缓存用户程序申请的微小对象。...type mcache struct { ......{ var c *mcache systemstack(func() { lock(&mheap_.lock) c = (*mcache)(mheap_.cachealloc.alloc()...多层次的分配 Cache,每个 P 上有一个 mcache,mcache 会为每个 size 最多缓存一个 span,用于无锁分配。
分配器由3种组件构成: mcache, mcentral, mheap。...mcache mcache:每个工作线程都会绑定一个mcache,本地缓存可用的 mspan资源,这样就可以直接给Goroutine分配,因为不存在多个Goroutine竞争的情况,所以不会消耗锁资源。...mcache的结构体定义: //path: /usr/local/go/src/runtime/mcache.go type mcache struct { alloc [numSpanClasses...当对象小于等于32KB大小时,使用 mcache的相应规格的 mspan进行分配。 mcentral mcentral:为所有 mcache提供切分好的 mspan资源。...大体上的分配流程: >32KB 的对象,直接从mheap上分配; <=16B 的对象使用mcache的tiny分配器分配; (16B,32KB] 的对象,首先计算对象的规格大小,然后使用mcache中相应规格大小的
中间件代码: memory-cache import type { NextFunction, Response, Request } from "express"; import mcache from...any, next: NextFunction) => { let key = req.originalUrl || req.url;//以接口路径作为key let cachedBody = mcache.get...import type { NextFunction, Response, Request } from "express"; import LRUCache from "lru-cache"; const mcache...any, next: NextFunction) => { let key = req.originalUrl || req.url;//以接口路径作为key let cacheData = mcache.get...(key, value); }; next(); } }; export default cache; export { mcache }; 思路: 定义中间件并且使用路径作为对象的
小于32KB内存块的分配策略 当程序里发生了32kb以下的小块内存申请时,Go会从一个叫做的mcache的本地缓存给程序分配内存。...从mcache中给程序分配内存 在Go的调度器模型里,每个线程M会绑定给一个处理器P,在单一粒度的时间里只能做多处理运行一个goroutine,每个P都会绑定一个上面说的本地缓存mcache。...当需要进行内存分配时,当前运行的goroutine会从mcache中查找可用的mspan。从本地mcache里分配内存时不需要加锁,这种分配策略效率更高。...其实mcache持有的这一系列的mspan并不都是统一大小的,而是按照大小,从8字节到32KB分了大概70类的msapn。 ?...mcache, mcentral, mheap是Go内存管理的三大组件,mcache管理线程在本地缓存的mspan;mcentral管理全局的mspan供所有线程使用;mheap管理Go的所有动态分配内存
mcentral mheap 为什么线程缓存mcache是被逻辑处理器p持有,而不是系统线程m?...解密 ---- 按照原TCMalloc的设计思想,线程缓存mcache确实应该被绑定到系统线程M上。 那么我们就假设:按照原TCMalloc的思想,把mcache绑定系统线程M上。...负责执行P调度过来的当前G 此阶段结论:以上的调度过程P的数量和M的数量是一一对应的,所以把mcache绑定系统线程M上和P看起来都可以。...当前就不能被复用,所以这样看起来是不是mcache绑定到p1上更合适。...之前的M的mcache就不会得到有效的复用,反而mcache绑定到P上就不存在这个问题,所以mcache绑定到P上更合适。 源码论证 通过Go的源码进一步证明我们的结论。
Go中,mcache线程缓存负责微对象和小对象(<32KB)的分配;弄清楚了mspan的内存结构,就很容易理解mcache,mcache只是包含了全部spanClass规格的mspan的一个内存结构,绑定在本地...小对象的分配逻辑是:先向mcache申请,mcache内存空间不够时,向mcentral申请,mcentral不够,则向页堆mheap申请,再不够就向操作系统申请。大对象直接向页堆mheap申请。...线程缓存mcache将runtime.mspan的内存结构剖析清楚了,再理解runtime.mcache就会非常简单。...每个G使用MCache时不需要加锁就可以获取到内存。...runtime.mcache的源码是:// src/runtime/mcache.gotype mcache struct { // 分配tiny对象的参数tiny uintptr
这个入口函数的主要工作就是分配内存以及触发gc(本文将只介绍内存分配),在进入真正的分配内存之前,此入口函数还会判断请求的是小内存分配还是大内存分配(32k作为分界线);小内存分配将调用runtime·MCache_Alloc...runtime·MHeap_Init(runtime·mheap, runtime·SysAlloc); // 从heap的cachealloc从分配MCache,挂在一个线程上。...m->mcache = runtime·allocmcache(); } 初始化过程主要是在折腾mcache和mheap两个部分,而mcentral在实际逻辑中是属于mheap的子模块,所以初始化过程就没明确的体现出来...Cache cache的实现主要在mcache.c源文件中,结构MCache定义在malloc.h中,从cache中申请内存的函数原型: void *runtime·MCache_Alloc(MCache...runtime·MCache_Alloc分配内存的过程是,根据参数sizeclass从list数组中取出一个内存块链表,如果这个链表不为空,就直接把第一个节点返回即可;如果链表是空,说明cache中没有满足此类大小的缓存内存
mspan、object、sizeclass、spanclass、heaparena、chunk的概念 接着介绍堆内存、栈内存的分配 回顾和总结 通过这个思路拆解的目录: Go内存管理架构(本篇内容) mcache...mcentral mheap 为什么线程缓存mcache是被逻辑处理器p持有,而不是系统线程m?...除此之外需要特别注意的地方:mcache被逻辑处理器p持有,而并不是被真正的系统线程m持有。...Object < 16B 小对象 16B =< Small Object <= 32KB 大对象 32KB < Large Object 为了清晰看出这三层的关系,这里以堆上分配小对象为例: 先去线程缓存mcache...逻辑上为: mcache:线程缓存 mcentral:中央缓存 mheap:堆内存 (下篇文章内容)线程缓存mcache是被逻辑处理器p持有,而不是系统线程m
mcache mcache与TCMalloc中的ThreadCache类似,mcache保存的是各种大小的Span,并按Span class分类,小对象直接从mcache分配内存,它起到了缓存的作用,并且可以无锁访问...但mcache与ThreadCache也有不同点,TCMalloc中是每个线程1个ThreadCache,Go中是每个P拥有1个mcache,因为在Go程序中,当前最多有GOMAXPROCS个线程在运行...,所以最多需要GOMAXPROCS个mcache就可以保证各线程对mcache的无锁访问,线程的运行又是与P绑定的,把mcache交给P刚刚好。...span没有空间怎么分配对象 span内的所有内存块都被占用时,没有剩余空间继续分配对象,mcache会向mcentral申请1个span,mcache拿到span后继续分配对象。...mcache向mcentral要span时,mcentral会先从nonempty搜索满足条件的span,如果每找到再从emtpy搜索满足条件的span,然后把找到的span交给mcache。
private static RepositoryFactory INSTANCE; /** * 使用Map实现一个内存缓存 */ HashMap mCache...Override public void add(Data data) { local.add(data); remote.add(data); mCache.put...public void update(Data data) { local.update(data); remote.update(data); mCache.put...param id * @return */ @Override public Data queryById(int id) { Data data = mCache.get...= null) { mCache.put(String.valueOf(id), data); } return data; } } 使用示例
. # TYPE go_memstats_mallocs_total counter go_memstats_mallocs_total 2408 # HELP go_memstats_mcache_inuse_bytes...Number of bytes in use by mcache structures. # TYPE go_memstats_mcache_inuse_bytes gauge go_memstats_mcache_inuse_bytes...14400 # HELP go_memstats_mcache_sys_bytes Number of bytes used for mcache structures obtained from system.... # TYPE go_memstats_mcache_sys_bytes gauge go_memstats_mcache_sys_bytes 16384 # HELP go_memstats_mspan_inuse_bytes
分配,「大对象」直接从堆上mheap中分配,如下图所示: 线程缓存mcache的tiny结构主要负责分配「微对象」 线程缓存mcache的alloc结构主要负责分配「小对象」 微对象的分配过程 微对象...线程缓存mcache的tiny内存充足,则直接分配「微对象」所需内存,图示如下: 2....线程缓存mcache的tiny内存不足,先去线程缓存mcache的alloc申请16B给tiny,再分配「微对象」所需内存,简易图示如下: 申请16B详细过程图示如下: 小对象的分配过程 小对象...线程缓存mcache的alloc充足,则直接分配「小对象」所需内存,简易图示如下: 详细分配过程图示如下: 2....来源优先级2:线程缓存mcache.alloc 小对象 16B =< Small Object <= 32KB 来源优先级1:线程缓存mcache.alloc 来源优先级2:中央缓存mcentral
领取专属 10元无门槛券
手把手带您无忧上云