【青训营】自动内存管理笔记

68 阅读3分钟

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

自动内存管理

在了解自动内存管理之前,先了解一些相关概念

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

对于GC算法有四个方面

  • 安全性(Safety):不能回收存活的对象基本要求

  • 吞吐率(Throughput):1-GC时间 / 程序执行总时间

  • 暂停时间(Pause time:stop the world(STW)业务是香感知

  • 内存开销(Space overhead) GC元数据开销

    其中标记内存主要有两种方法

  • 追踪垃圾回收(Tracing garbage collection)

  • 引用计数(Reference counting)

自动内存管理

动态内存管理就是管理程序在运行时候系统管理动态内存避免手动内存管理,专注于实现业务逻辑

  • 动态内存 程序在运行时根据需求动态分配的内存:ma11oc()
  • 动态内存管理的三个任务

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

追踪垃圾回收

步骤:

image-20230119224233035

  1. 标记根对象(程序中静态变量,全局变量,常量,线程栈等)

  2. 标记可以到达的对象标记(求指针指向关系的传递闭包:从根对象出发,找到所有可达对象)

  3. 清理:所有不可达对象

    • 将存活对象复制到另外的内存空间(Copying GC)
    • 将死亡对像的内存示记为“可分配"(Mark-sweep GO)
    • 移动并整理存活对象(Mark-compact GC)

其中根据对象的生命周期可以使用不同的标记和清理策略

我们可以发现很多对象分配出来就不使用了,这样我们就可以根据不同的方法提高效率

我们可以为每个对象记录一个年龄 :经历GC的次数。这样我么就可以将对象分为年轻代和老年代

Copying GC

  • 当存活的对象(大多数是年轻代)少时,对内存的操作就少,这时GC的吞吐率高

    image-20230119225636780

Mark-sweep GC

  • Mark-sweep GC 是将空闲的内存清理,所以适合老年代

image-20230119224905846

引用计数

image-20230119225951012

  • 每个对象都有一个与之关联的引用数目

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

  • 优点 内存管理的操作被平滩到程序执行过程中

  • 缺点

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

Go内存管理及优化

Go内存分配---分块

Go在内存中先将内存提前分配成一个一个小块,当对象需要使用的时候就将最为相近大小的内存分配给对象

  • 提前将内存分块

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

image-20230119230823151

image-20230119230944988