| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> 系统运维 -> Go内存管理 -> 正文阅读 |
|
[系统运维]Go内存管理 |
Go内存管理 目录 一、虚拟内存是什么?为什么会有虚拟内存?操作系统管理内存的机制——为什么要设置虚拟内存? - wj_hubei - 博客园 总结如下 : 使用虚拟内存和不使用虚拟内存的区别 :
虚拟内存的工作原理: 当一个进程试图访问虚拟地址空间中的某个数据时,会经历下面两种情况的过程: 1、CPU想访问某个虚拟内存地址,找到进程对应的页表中的条目,判断有效位, 如果有效位为1,说明在页表条目中的物理内存地址不为空,根据物理内存地址,访问物理内存中的内容,返回。 2、CPU想访问某个虚拟内存地址,找到进程对应的页表中的条目,判断有效位,如果有效位为0,但页表条目中还有地址,这个地址是磁盘空间的地址,这时触发缺页异常,系统把物理内存中的一些数据拷贝到磁盘上,腾出所需的空间,并且更新页表。此时重新执行访问之前虚拟内存的指令,就会发现变成了情况1。 Linux虚拟内存空间分布如下: Linux虚拟内存空间分布_wyq_5的博客-CSDN博客_虚拟内存分布 .reserve(预留)段 :一共占用128M,属于预留空间,进程是禁止访问的。 .text(代码段) :可执行文件加载到内存中的只有数据和指令之分,而指令被存放在.text段中,一般是共享的,编译时确定,只读,不允许修改。 .data : 存放在编译阶段(而非运行时)就能确定的数据,可读可写。也就是通常所说的静态存储区,赋了初值的全局变量和赋初值的静态变量存放在这个区域,常量也存放在这个区域。 .bss段 : 通常用来存放程序中未初始化以及初始化为0的全局/静态变量的一块内存区域,在程序载入时由内核清0 .heap(堆) : 用于存放进程运行时动态分配的内存,可动态扩张或缩减,这块内存由程序员自己管理,通过malloc/new可以申请内存,free/delete用来释放内存,heap的地址从低向高扩展,是不连续的空间 共享库(libc.so) .stack(栈) : 记录函数调用过程相关的维护性信息,栈的地址从高地址向低地址扩展,是连续的内存区域 我们写好的程序编译完变成可执行文件,在被执行的时候, 可执行文件被分成了代码段和数据段两部分, 代码段里面存的是各种if、else、for、赋值等机器指令。而数据段里面存的是定义的各种变量、常量等。数据段又分全局变量、常量等在编译期就能确定大小的数据,和一些只有在运行时才会赋值和初始化的变量。 程序在被执行的时候, 一行行的读取机器指令,其中可能某些指令会操作数据, 这个时候可能就需要去数据段里面找对应的变量的地址, 然后修改变量、给变量分配内存、回收内存等。 其中的代码段和数据段是一开始就加载进内存里面了,堆和栈是动态的。 二、内存分配器是什么?解决了什么问题自己动手实现一个malloc内存分配器 | 30图【图文】_mb600aa3928e8ce_51CTO博客 我们现在思考另外一个问题, 假如你现在有一排1000米的地要租出去(半条街),不同的租户需要的面积不同, 你怎么管理这块地做到以下两点:
假设我们不能使用额外的账簿之类的来记录哪些地租给了谁,只能在我们那1000米里面记录。那我们还要解决怎么知道哪些地是空闲的, 哪些地是租出去了的。
最终我们租给用户的地可能是这样的 : 内存分配器其实要解决的是相同的问题, 不过他管理的是堆内存, 而存放的是程序员申请的内存或各种类型、各种大小的变量, 而且设计得要更加复杂等。 如果对里面有足够多的内存供分配, 分配器直接分配, 如果没有, 则分配器将会通过系统调用函数 brk 来扩展堆,通常是增加变量 MMAP_THRESHOLD 的默认值 (128KB)。 三、Go里面的内存分配器go的内存分配器同样要做到提高内存使用率(减少内存碎片)和更快的分配和释放内存。 而且多线程下在分配、释放的时候是需要加锁的, 也影响了分配、释放的效率。 Go的分配器与 TC-Malloc非常相似, TCMalloc(Thread Cache Malloc)的核心思想是将内存分解为多层、多块,相当于使用了多级缓存,减小内存锁的粒度和查找需要的内存大小空间的时间。 TCMalloc : 组件整体设计如下: 各部分作用和定位如下 : Page : 操作系统对内存管理以页为单位, 这里也是一样, 不过页的大小可能跟操作系统中不一样,倍数关系。 Span : 一组连续的page组成的小数组合为一个span,这样我们就可以通过底层page的数量将span分成大小不一的多种span。 ThreadCache : 线程缓存, 因为所有线程使用的是同一块虚拟内存, 所以需要加锁,但是给每个线程单独分配一个缓存池之后, 线程操作自己的缓存池就不存在并发的问题, 也就不需要加锁。一个cache包含多个空闲内存块链表, 每条链表中存储的是相同类型的span,不同的链表中span的大小不同,分配内存的时候直接先找到合适的span链表, 再在链表中分配 CentralCache : 缓存中心, 里面跟ThreadCache一样存的是各种大小的span链表, 不同之处在于他是全局的,当ThreadCache里面的span分配完之后会来这里获取, 当然这是要加锁的。 PageHeap : 是对整个堆内存的抽象,存的也是若干保存各种span的链表,CentralCache内存或者过多时,会从这里获取或者返回到这里。除了存各种span链表, 还存储了 large span set, 这个是用来保存中、大对象的。 小对象大小:0~256KB 中对象大小:257~1MB 大对象大小:>1MB 小对象的分配流程:ThreadCache -> CentralCache -> HeapPage,大部分时候,ThreadCache缓存都是足够的,不需要去访问CentralCache和HeapPage,无系统调用配合无锁分配,分配效率是非常高的。 中对象分配流程:直接在PageHeap中选择适当的大小即可,128 Page的Span所保存的最大内存就是1MB。 大对象分配流程:从large span set选择合适数量的页面组成span,用来存储数据。 总结起来就是, 我们使用内存的时候大多一次申请的都是较小的内存块, 我划分出多种小内存块放到线程自己的内存池中,访问自己线程池中的内存不需要加锁。 这些内存块之间排列可以比较紧密, 我分配、回收的时候先确定需要哪种类型的小内存块, 然后直接去获取提前确定大小的内存? 减少了内存碎片。 核心思想是使用了缓存, 以空间换时间。 Go的内存管理借鉴了TCMalloc, 也是利用了缓存, 他的设计如下 : 各部分作用和定位如下 : Page : 与TCMalloc 中一样。 Span : 与TCMalloc 中一样, 代码中为mspan, 不同点在于每种大小(page数量相同)的span都有两类,scan类型的span包含指针, 在GC的时候会被扫描, noscan类型的span不包含指针, 在GC的时候不会被扫描。 Mcanche : 与TCMalloc 中 ThreadCache 类似, 只不过因为go使用GPM模型, 所以Mcanche不属于线程, 而是属于每个P(算是P的一部分)。 Mcentral : 与 TCMalloc 中CentralCache 类似,不同点在于CentralCache是每个级别的Span只有1条链表,mcache是每个级别的Span有2条链表。这两条链表可以理解为被分配过的内存段nonempty链表(可能有些被释放了的也在这里)和未被分配过的内存段empty链表(全部可用)。Mcanche在获取Mcentral中的span时, 先去非空那条找, 看有没有之前分出去后被归还的span, 没有的话再去另一条链表拿一个新的给他。 Mheap : 与 TCMalloc 中PageHeap 类似, 不同点在于mheap把Span组织成了树结构,而不是链表,并且还是2棵树,然后把Span分配到heapArena进行管理,它包含地址映射和span是否包含指针等位图,这样做的主要原因是为了更高效的利用内存:分配、回收和再利用。 Go中的内存分类并不像TCMalloc那样分成小、中、大对象,但是它的小对象里又细分了一个Tiny对象,Tiny对象指大小在1Byte到16Byte之间并且不包含指针的对象。小对象和大对象只用大小划定,无其他区分。 小对象是在mcache中分配的,而大对象是直接从mheap分配的。 Go中寻找span的流程如下: 1、计算对象所需内存大小size 2、根据size到size class映射,计算出所需的size class 3、根据size class和对象是否包含指针计算出span class 4、获取该span class指向的span 四、Go内存分配和GC流程【golang】GC详解 - Go语言中文网 - Golang中文社区 内存分配 : 首先会检查GC是否在工作中, 如果GC在工作中并且当前的G分配了一定大小的内存则需要协助GC做一定的工作, 这个机制叫GC Assist, 用于防止分配内存太快导致GC回收跟不上的情况发生. 之后会判断是小对象还是大对象, 如果是大对象则直接调用largeAlloc从堆中分配, 如果是小对象分3个阶段获取可用的span, 然后从span中分配对象: 1、首先从P的缓存(mcache)获取 2、从全局缓存(mcentral)获取, 全局缓存中有可用的span的列表 3、从mheap获取, mheap中也有span的自由列表, 如果都获取失败则从arena区域分配 GC流程图 : 在GC过程中会有两种后台任务(G), 一种是标记用的后台任务, 一种是清扫用的后台任务. 标记用的后台任务会在需要时启动, 可以同时工作的后台任务数量大约是P的数量的25%, 也就是go所讲的让25%的cpu用在GC上的根据. 清扫用的后台任务在程序启动时会启动一个, 进入清扫阶段时唤醒. 目前整个GC流程会进行两次STW(Stop The World), 第一次是Mark阶段的开始, 第二次是Mark Termination阶段. 第一次STW会准备根对象的扫描, 启动写屏障(Write Barrier)和辅助GC(mutator assist). 第二次STW会重新扫描部分根对象, 禁用写屏障(Write Barrier)和辅助GC(mutator assist). 需要注意的是, 不是所有根对象的扫描都需要STW, 例如扫描栈上的对象只需要停止拥有该栈的G. |
|
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/15 13:22:20- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |