Go内存管理|青训营笔记

67 阅读3分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第4天,今天学习了Go内存管理相关的知识下面是我的笔记

Go内存管理

1、内存管理优化

1.1、性能优化的层面

  • 业务代码:业务层的优化
  • SDK+基础库:语言运行时优化
  • 性能分析工具:pprof,依靠数据而不是猜测

1.2、自动内存管理

  • 动态内存,malloc,程序运行时根据需要分配
  • 垃圾回收机制:自动GC,避免手动内存管理,专注于实现业务逻辑。
  • GC的主要任务为分配空间给新的对象,识别对象的死活,回收死亡对象的内存空间

1.3、GC算法的原理及评价

  • 对象只有在指针指向关系不可达时才会被回收

  • 引用计数算法:

    在对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加一;当引用失效时,计数器值就减一;任何时刻计数器为零的对象就是不可能再被使用的。

  • 标记根对象:静态变量,全局变量,常量,线程栈等

  • 标记:找到可达对象(一般这一步是必须的)

    清理:所有不可达的对象

  1. 标记复制算法:copying GC 将存活对象复制到另外的内存空间。

    1. Fenichel提出了一种称为“半区复制”(Semispace Copying)的垃圾收集算法,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。这有可能造成空间浪费
    2. Andrew Appel针对具备“朝生夕灭”特点的对象,提出了一种更优化的半区复制分代策略,现在称为“Appel式回收”。Appel式回收的具体做法是把新生代分为一块较大的Eden空间和两块较小的Survivor空间,每次分配内存只使用Eden和其中一块Survivor。发生垃圾搜集时,将Eden和Survivor中仍然存活的对象一次性复制到另外一块Survivor空间上。
  2. 标记清理算法:将死亡对象的内存标记为可分配

    它的主要缺点有两个:

    1. 第一个是执行效率不稳定,堆中包含大量对象,而且其中大部分是需要被回收的,这时必须进行大量标记和清除的动作,导致标记和清除两个过程的执行效率都随对象数量增长而降低;

    2. 第二个是内存空间的碎片化问题,标记、清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致当以后在程序运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。标记-清除算法的执行过程如下图所示

  3. 标记整理算法:移动并且整理存活对象。

    老年代对象的存亡特征,1974年Edward Lueders提出了另外一种有针对性的“标记-整理”(Mark-Compact)算法,其中的标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理**,而是让所有存活的对象都向内存空间一端移动,然后直接清理掉边界以外的内存**。

2、Go内存分配

  • 提前将内存分块

  • 调用系统调用mmap()向os申请内存,根据对象大小将内存分为大块小块

  • 缓存机制

  • Balanced GC 的使用

    本质上是将多个小对象的分配合并为一个大对象,使用copyGC的算法管理小对象。

    if g.ab.end - g.ab.top < size {
        // Allocate a new allocation buffer
    }
    addr := g.ab.top
    g.ab.top += size
    return addr