GC: (Garbage Collector, 垃圾回收器)
在 CPython 中
垃圾回收采用了引用计数+ 标记-清除 + 分代回收 的组合回收方法。
即给每个对象设置引用计数,当引用计数为0就立即回收内存;
但是由于对象之间存在互相引用的问题,单单使用引用计数是不够的,于是引入了标记-清除算法来回收来遍历引用到了的对象,将没有引用到的对象销毁
标记-清除方法需要消耗很多时间,为了提升效率,又引入了分代回收,将多次检测都有效的对象升级为下一代,代数越高则检查的次数就越少(共3代,每代实际上就是一个链表),以此来增加效率
Micropython GC
与 CPython不同,Micropython 只使用了 标记-清除算法,可以说是教科书般的标记-清除算法实现了(官方的说法2333)
这里 GC 不单单包括了垃圾回收,实际上内存分配方法也包含在了里面,大致上包含了以下几个点:
系统预分配一块内存给GC使用,之后所有的操作都在这块内存上进行
然后分配和释放时根据分配算法进行分配和释放(后面再详细说明)
在以下几个情况执行回收(使用标记-清除算法(mark-sweep):
无法分配内存
设置了回收阈值(threshold),当距离上几次回收后分配的空间(块)数量打到了设置的阈值,则自动进行回收。当然为了减少回收次数(某种意义上的)可以禁用这个功能
手动调用函数进行回收
Micropython 中的 GC 详细分析
数据储存结构
为了简化程序,uPy将初始化时获得的一段连续内存区域分成3部分:
储存数据块分配信息,标记内存分配情况,称为 ATB(alloc table)
储存销毁对象时是否需要调用析构函数(finaliser)(__del__),称为FTB(finaliser table)
实际储存数据的区域,称为内存池(pool),由多个块(block)组成
uPy 分配内存的最小单位是块(block)(比如设置为16或者32字节),所以ATB FTB中记录也是以块为单位记录的。
其中 ATB 标记每个块的状态,包括 free, head(多个连续分配的块的开始(头)), tail(多个连续分配的块的除了头部块以外的块), mark(标记了的头部,在回收时(collect)中才会标记) 几种状态,每两位表示一个block状态,即每个字节可以表示4个block的状态
FTB 则标记是否使用 finaliser,每位表示一个block是否使用finaliser,即每个字节表示8个block是否使用finaliser
名词
block: 块,分配的最小单位,比如设置为16或者32字节。
finaliser:这里可理解成析构,调用 类的__del__方法,(mpy目前(2019.5)还没有实现调用用户类的__del__方法)
ATB