zoukankan      html  css  js  c++  java
  • go 内存分配

    一、概述

    (一)基本策略

    • 1、程序每次先从系统申请一大块内存(比如1MB),减少向系统申请内存频率,也就是说,先给我整块大的,以后少找你,不够了,再找你要一块大的;
    • 2、然后程序将大块内存,按照特定大小(后文将提到的sizeClass,单位可以理解为8字节)切分为小块(object),小块构成链表(span);
    • 3、为对象分配内存时,只需要按照对象的大小(sizeClass),从满足该大小的链表中,获取一小块即可;
    • 4、回收对象内存时,将该小块内存重新归还到原链表,一遍复用;
    • 5、如果闲置的很多,则会尝试归还部分内存给系统,降低程序整体开销
      注意上述回收,是指内存分配器的回收,内存分配器只管理内存块,并不关心对象状态,且不会主动回收内存。只有在垃圾回收器完成回收操作后,触发内存分配器回收内存

    (二)内存分配器,将内存块分为两种。

    • span: 由地址连续的页(page)组成
    • object:将span按照特定大小的块切分成多个小块(object),每个小块用于对象存储。按8字节倍数分为N种

    是不是有点懵,其实可以这么理解,就像上学时写作业

    • 程序向系统申请的一大块内存(比如1MB),这一大块内存,可以看成我们新买了的作业本;
    • 接着,制定一些书签,书签上需要标明sizeClass,当后续给不同大小的对象分配内存时,能够快速定位;
    • 作业本一些连续的页(page)就组成了span,然后对每页上的一行一行进行分块,可以理解为分object,但是需要根据书签(sizeClass)大小分块,比如书签为1的,我们就像书签下的这些页上的每一行分成一块object

    (三)管理组件

    Go起点高,直接采用tcMalloc(线程缓存内存)成熟架构
    分配器由三种组件组成(组件去管理span,获取和释放object块):

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

    (四)几个重要的结构体对象

    
    _NUmsizeClasses = 67
    
    type mspan struct {
        next *mspan  //双向链表,指向下一个span
        prev *mspan
        start pageID  //起始序号
        npages  uintptr //页数
        freelist  gclinkptr  //待分配的object 链表
    }
    
    type mcache struct {
    	alloc [_NUmsizeClasses]*mspan   //以sizeclass为索引管理多个用于分配的span
    }
    
    type mcentral struct {
    	szieclass int32  //规格大小
    	noempty mspan   //链表,span中还有可用的空闲object
    	empty mspan //链表,span中没有可用的空闲object
    }
    
    type mheap struct {
    	free  [_MaxMHeapList]mspan   //页数在127以内的闲置 span 链表数组。因为每页大小是固定的,以page为索引,管理对应的span
    	freelarge  msapn      //页数大于127 (大于1MB)span 大链表数组,大对象直接从heap分配回收
    	central [_NUmsizeClasses]struct {
    		mcentral mcentral
    	}
    }
    
    

    分配器按照页数区分不同大小的span,比如 mheap ,以页数为单位将span 存放在管理数组中,需要时就以页数为索引进行查找。当然span大小并非
    固定不变,在获取闲置的span时,如果没有找到合适大小的span,那就返回页数更多的span,此时引发裁剪操作,多余部分将构成新的span 被放回管理数组。分配器
    还会尝试将相邻的空闲 span 合并,以构成更大的内存块,减少碎片,实现更灵活的分配策略。

    用于存储的object,按照8字节倍数分为N种。比如说,大小为24字节的object可以用来存储范围为17——24字节的对象。虽然会有一些浪费,但是分配器只需要面对有限几种规格(sizeclass)的小块内存,优化了分配和复用策略。
    同时,分配器会尝试将多个微小的对象组合到一个object内存块,以节约内存

    分配器初始化时,会构建对照表,存储大小和规格的对应关系,包括用来切分的span页数(一页8KB)。
    若对象大小超出特定的阈值(32KB),会被当做大对象特殊处理。

    (五)分配流程

    • 1、计算待分配对象对应规格(sizeClass),也就是要几个8字节;
    • 2、从cache.alloc 数组中找到对应规格的span;
    • 3、从span.freelsit 链表中获取可用的object;
    • 4、若没有可用的,即span.freelsit为空,从central获取span
    • 5、如central.noempty为空,从heap.free 或者 heap.freelarge 中获取span,并切分object 链表;
    • 6、若还是没有找到大小合适且空闲的span,则向操作系统申请新内存块

    (六)释放流程

    • 1、将标记为可回收的object交给所属span.freelist
    • 2、该span被放回central,也就是拼接至mcentral.nonempty链表后,但是不要以为mcache.alloc 数组中就没有该span,
      该span还在,任然保持对span的指针引用;
    • 3、如果span收回了所有的object,则将其还给heap,即mheap.freelist,以便重新分割复用;
    • 4、定期扫描heap长时间闲置的span,释放其占用的内存,也就是还给系统

    注意,以上不包含大对象,他直接从heap分配和回收
    作为工作线程私有且不被共享的cache是实现高性能无锁分配的关键,而central的作用是在多个cache间提高object利用率,避免内存浪费,将span归还heap,是为了在不同规格object需求间平衡。
    在计算机科学里,没有什么问题是不能通过中间过程解决的,所以很多架构,都会有中间件这个存在。

    假如cache获取span的一部分object后,那么该span中还有许多剩余的object,但是回收操作将该span交还给central,该span还可以给其他线程cache1,cache2...使用,cache并没有持有span,只是用span中object。
    而归还到heap的过程,则可以这部分内存,被其他不同规格大小需求使用,重新切分。

  • 相关阅读:
    linux 删除已输入的命令行
    LAMP编译参数查看
    mysql忘记密码的重置方法
    cmd大全_练习
    Parallel WebDriver executions using TestNG
    Internet Explorer for Mac the Easy Way: Run IE 7, IE8, & IE9 Free in a Virtual Machine
    linux delete files older than 3 days
    5 commands to check memory usage on Linux
    Base64 Encoding / Decoding in Node.js
    10 Useful du (Disk Usage) Commands to Find Disk Usage of Files and Directories
  • 原文地址:https://www.cnblogs.com/fanzou/p/13750920.html
Copyright © 2011-2022 走看看