内存管理 | 青训营笔记

96 阅读4分钟

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

课程内容

  • 自动内存管理
  • Go内存管理及优化
  • 编译器静和静态分析
  • Go编译器优化

自动内存管理

Go类似Java,都有自带的垃圾回收,自动内存管理机制,方便开发者专注于实现业务逻辑,保证内存使用的正确性和安全性(double-free problem,use-after-free problem)

三个任务

  • 为新对象分配空间
  • 找到存活对象
  • 回收死亡对象的内存空间

概念

  • Mutator 业务线程: 负责分配新对象,修改对象指向关系
  • Collector GC线程: 负责找到存活对象,回收死亡对象的内存空间
  • Serial GC: 只有一个 collector
  • Parallel GC:并行GC,支持多个 collectors 同时回收的GC算法
  • Concurrent GC:并发GC,支持 mutator(s) 和 collector(s) 同时执行的 GC算法

Collectors 需要感知对象指向关系的改变

追踪垃圾回收 -Tracing garbage collection

  • 回收条件: 不可达对象

  • 回收过程:
    ① 标记根对象(GC roots):静态变量,全局变量,常量,线程栈等
    ② 标记:找到所有可达对象
    ③ 清理:回收所有不可达对象占据的内存空间

  • 三种垃圾回收方式:

  • a. Copying GC: 将存活对象从一块内存空间复制到另外一块内存空间,原先的空间可以直接进行对象分配.

  • b. Mark-sweep GC:将死亡对象所指内存块标记为可分配,使用 free list 管理可分配空间

  • c. Mark-compact GC:将存活对象复制到同一块内存区域的开头

引用计数

  • 每个对象都有一个与之关联的引用数目
  • 对象存活条件:当且仅当引用数大于0

很明显,这个方案的优点是内存管理操作被平摊到程序运行中(指针传递的过程中进行引用计数的增减),而且方便专注业务开发,不用关心 runtime 细节;但缺点也很明显,开销比较大,多线程下对引用计数器修改需要原子操作保证其原子性和并发安全.

Go内存管理及优化

Go 内存管理

TCMalloc(TC is short for thread caching) 是 Google 开发的内存分配器,在不少项目中都有使用,例如在 Golang 中就使用了类似的算法进行内存分配。它具有现代化内存分配器的基本特征:对抗内存碎片、在多核处理器能够 scale。据称,它的内存分配速度是 glibc2.3 中实现的 malloc的数倍。

  • 其会提前将内存分块:先调用 mmap() 向os申请一大块内存(mb),然后将其分成多个大块(kb),然后将大块继续分成小块用于对象分配(B);对于包含指针的对象,GC需要扫描(scan).
  • 分配对象的块不是严格按照2的幂次的,不然会极大浪费 image.png
  • Go有个多级缓冲机制,内存被GC回收以后,也不会立刻归还OS,而是保存至GO runtime内部,避免频繁向os申请内存[mcache -> mecntal -> mheap -> Virutal Memory]

字节跳动优化方案

  • 从线上profiling发现,分配小对象经常调用 mallocgc(),导致CPU占用较高;
  • Balanced GC:将 noscan 对象在 per-g allocation buffer (GAB) 上分配,并使用移动对象 GC 管理这部分内存,提高对象分配和回收效率. b2430d70a8e642b593f9de816e1359d5_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.webp
  • 每个 g 会附加一个较大的 allocation buffer (例如 1 KB) 用来分配小于 128 B 的 noscan 小对象
  • 使用了bump pointer 的 style 来进行对象分配; gc-1.png

编译器和静态分析

静态分析不执行代码,而是通过推导程序的行为,分析程序的控制流,数据流等,如课程示例图将程序转换成控制流图.

Go编译器优化

  • 目的:①用户无感知,重新编译即可获得性能收益 ②有通用的优化手段
  • 现状:采用的优化较少,更追求缩短编译时间,所以没有进行复杂的代码分析和优化
  • 思路: 主要面向后端开发的长期项目,用适当增加编译时间获得更高性能的代码
  • 函数内联(和C++类似,一些不是经常调用的函数可以直接写到调用位置上,这样消除了调用函数的开销,能提高一定性能;缺点是函数体会变大,增加了编译时间.)
  • 逃逸分析

个人总结

今天的课程是语言方面的最后一门,后面将会开始讲框架应用;内存管理在每个语言中都是十分重要的,它在一定程度上决定了语言的特性,C++将这个交给了开发者,使得开发者需要更多精力注意这方面,但是好处是自由度足够;而Go,java语言有自动内存管理,让开发者更能专注于业务逻辑,而不需要过于担心内存泄漏等问题.课程中还引用了字节的优化案例,让人眼前一亮,希望后续课程也多多加入这种内容~~

文章参考