GO语言内存管理 | 青训营笔记

127 阅读4分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 3 天

笔记内容为个人上课对ppt以及自己不了解知识的记录

性能优化与自动内存管理

自动内存管理

概念

  • 动态内存,自动内存管理的对象是,动态内存

  • 自动内存管理(垃圾回收):由程序语言的运行时系统管理动态内存

    • 避免手动内存管理,更加关注业务
    • 保证内存使用的安全性
  • 三个任务

    • 为对象分配空间
    • 找到存活的对象
    • 回收死亡的对象(不可达)

Mutator:业务线程(用户启动的线程,go routine),分配新对象,修改对象指向关系

Collector: GC线程,找到存活对象,回收死亡对象的内存空间

Serial GC :只有一个collector

Parallel GC :支持多个collectors同时回收的GC算法(并行)

Concurrent GC : mutator(s)和collector(s)可以同时执行(并发)

collector线程做内存的标记和回收

Mutator线程使用内存

tracing garbage collection

  • 追踪垃圾回收

对象被回收的条件:指针指向关系不可达的对象

回收流程

标记根对象

  • 静态变量,全局变量,常量,线程栈等

  • 这里的跟对象是指指向堆的指针

标记:找到可达对象

  • 根据指针指向关系,从跟对象出发,找到所有可达对象

  • 因为是指针链,所以能形成链条,最后一个是对象

清理所有不可达对象

  • 将存活对象复制到另外的内存空间

    清空原来的大区域

  • 将死亡对象的空间标记为“可分配(Mark-sweep GC)”

    白色为可分配空间

  • 移动并整理存活对象(紧凑compact-GC)

策略的选择方式: 根据对象的生命周期,选择不同的标记清理策略

Generational GC

  • 分代假说:大多数的对象很快就死掉了

  • Intuition:很多对象在分配出来之后很快就不使用了

  • 每个对象都有年龄:经历的GC次数

  • 目的:对年轻和老年的对象,制定不同的GC策略,降低整体内存管理的开销

  • 不同的年龄的对象,处于heap的不同区域

  • 年轻代

    • 常规的对象分配

    • 由于存活对象很少(很多对象分配出来很快就不用了) ,可以开用copying collection

    • GC吞吐率很高

  • 老年代

    • 对象趋向于一直存活,反复复制开销比较大

    • 可以采用mark-sweep collection的方式

      (碎片较多时可以使用一次compact-GC进行压缩)

引用计数

  • 每一个对象(分配的一个heap内存)都有一个与之关联的引用数目

  • 对象存活的条件:当且仅当引用数大于0

  • 优点

    • 内存管理的操作被平摊到程序的执行过程中
    • 内存管理不需要了解runtime的实现细节:C++智能指针
  • 缺点

    • 维护引用计数的开销比较大:通过原子操保证对引用计数操作的原子性和可见性
    • 无法回收环形数据结构-----weak reference
    • 内存开销:每个对象都要引入额外的内存空间存储引用计数
    • 回收内存时仍然可能引发暂停

go内存管理及优化

go内存分配——分块

  • 目标:为对象在heap上分配内存

  • 提前将内存分块

    • 调用系统调用mmap()向os申请一大块内存,例如4MB
    • 将内存划分为大块例如8KB,称为mspan
    • 再将大块继续划分为小块,用于对象分配
    • noscan mspan:分配不包含指针的对象——GC不需要扫描
    • scan mspan : 分配包含指针的对象——GC需要扫描TODO
  • 对象分配:根据对象的大小,选择最合适的块返回

go内存分配——缓存

  • TCMalloc:thread caching

  • 内存分配流程: g—>m—>p—>mcache—>mspan—>memory block—>return pointer

Balanced GC

  • 每个g(go routine)都绑定一大块内存(1KB),称作goroutine allocation buffer(GAB)

  • GAB用于noscan类型的小对象分配:<128B

  • 使用三个指针维护GAB:base, end, top

  • Bump pointer(指针碰撞)风格对象分配

    • 因为GAB是每个goroutine独有的,所以不需要和其他g做互斥分配
    • 分配动作简单高效率

  • GAB对于Go内存管理来说,是一个大对象

    • 下图:一个mspan中的一个内存对象就是一个gab
  • 本质:将多个小对象的分配,合并为一个大对象的分配(大对象已经通过内存分配路径g—>m—>p.....分配完成了)

  • 问题:GAB对象分配方式会导致内存被延迟释放:因为go使用标记回收,而大对象内只要有任何一个小对象,都会被标记为在使用,无法回收

  • 方案:移动GAB中存活的对象

    • 当GAB总大小超过一定域值,就将GAB中存活的对象复制到另外分配的GAB中
    • 原先的GAB可以释放,避免内存泄漏
    • 本质:用copying GC算法管理小对象,并且减少了内存分配次数

编译器和静态分析

函数内敛

  • Beast Mode

    • 函数内涵后拓展了函数边界,更多的对象不逃逸,可以在栈上分配
    • GC管理的内存少了,调用频率小了
  • 逃逸分析

    对象在作用域外能被访问