C++的简易内存管理工具实现--垃圾回收

Writing a Mark-Sweep Garbage Collector

接下来介绍了我在实现内存分配管理工具的具体细节,其中包括内存分配、内存池、垃圾回收的实现。
其他文章请详见:
内存分配:添加链接描述
内存池:添加链接描述
垃圾回收:添加链接描述
项目代码:添加链接描述

Tracing vs. Direct collectors

所有 GC 算法都分为tracing和direct。
在这里插入图片描述
尽管被称为“垃圾”收集器,但跟踪收集器实际上并不识别垃圾。相反,情况恰恰相反——它们识别活着的对象,通过扩展将其他一切视为垃圾。

Mark-Sweep 是一个跟踪收集器,因此我们将在我们的实现中进行跟踪阶段。
在这里插入图片描述
由于我们选择在 C++ 中实现收集器,我们现在将讨论与它的运行时系统相关的一些细节。

C++ specifics

下面是一些与 C++ 运行时相关的细节,它们会影响我们的实现。

Exposed pointers semantics

正如我们之前讨论的讨论的那样,C++ 将指针语义暴露给用户级。这意味着,我们可以获得一个指向对象的指针,并将其作为可观察值存储在某个变量中。

这反过来使得在 C++ 运行时实现移动(如 Mark-Compact)或复制(Generational、Semi-Space 收集器)变得更加困难:如果我们重新定位一个对象,所有指针都将失效,并且包含不正确的地址。

对于 Java、JavaScript 和其他不直接在用户代码中使用原始内存的语言,它要简单得多,还有许多其他选项。

幸运的是,Mark-Sweep 是一个非移动收集器,这意味着我们可以用 C++ 实现它,这是一项非常有趣的工程任务,因为我们需要接触堆栈、寄存器、一些汇编的概念,以及其他的东西。

Pointer or number?

为了实现跟踪算法,我们应该能够获得对象形状内的所有(子)指针。然后跟随子指针,再次获取它们的指针,等等——遍历整个对象图。
然而,一旦我们在 C++ 中获得了一个指针,通常我们无法判断它是否真的是一个指针,或者只是一个简单的数字——回想一下,垃圾收集器在只有字节的较低级别工作,但不是实际的具体 C++结构。
同样,托管高级语言,例如 Java、JS 等,会跟踪值类型,并且可以轻松区分指针和其他值。为此,此类语言使用不同类型的值编码,其中最流行的是标记指针和NaN 装箱。编码总是假设解码的逆操作,这通常发生在运行时。它可能会减慢操作速度,但它是实现大多数动态编程语言的事实上的标准。

在 C++ 中,而不是编码值(并在运行时不断解码它们),我们将使用带有“鸭子测试”的保守方法——如果它看起来像一个指针,并且真的指向一个已知的堆地址,那么它必须是一个指针,我们需要跟踪它。毫不奇怪,这些收藏家被称为保守派。

GC roots

垃圾收集器根对象(或简称根)为出发点,从我们开始我们的跟踪分析。这些对象保证是存活的,因为通常由堆栈上的全局或局部变量指向。

更高级别的虚拟机可能会出于 GC 的目的跟踪所有根,从而使诸如此类的过程getRoots更加简单。但是在 C++ 中,我们必须手动遍历堆栈,保守地收集根。

此外,一些局部变量可能会放在 CPU 寄存器中,我们也必须遍历它们。幸运的是,我们可以使用一种将所有寄存器压入堆栈的技巧,这将为我们节省一些时间和实现工作。

好的,现在让我们直接进入实现,从对象图开始。

Implementation

我们将在这个实现中使用自顶向下的方法,从高级和基本概念开始,将 C++ 的具体细节留到最后。

Traceable objects

我们实现的关键部分将是Traceable对象,它将跟踪垃圾收集器所需的所有信息。稍后我们将描述Traceable结构体的细节,现在让我们展示如何创建简单地继承它的结构体:
我们实现的关键部分将是Traceable对象,它将跟踪垃圾收集器所需的所有信息。稍后我们将描述Traceable结构体的细节,现在让我们展示如何创建简单地继承它的结构体:

/**
 * Traceable Node structure.
 *
 * Contains the name of the node, and the
 * pointers to left and right sub-nodes,
 * forming a tree.
 */
struct Node : public Traceable {
   
   
  char name;
 
  Node *left;
  Node *right;
};

所有可追踪对象都可以访问所谓的Object header,其中包含 GC 元信息。让我们看看它是什么。

Object header

顾名思义,Mark-Sweep 收集器由两个阶段组成:标记阶段(活动对象的跟踪)和清除阶段(垃圾回收)。要将对象标记为活动的,收集器需要将此标志存储在某处,这就是对象头发挥作用的地方。
我们的头将包含两个字段:标记位和对象的大小:

/**
 * Object header.
 */
struct ObjectHeader {
   
   
  bool marked;
  size_t size;
};

每个可跟踪对象都可以访问其标头(通过基Traceable类):

// Create a node:
auto node = new Node {
   
   .name = 'A'};
 
// Obtain the header:
auto header = node->getHeader();
 
std::cout << header->marked; // false
std::cout << header->size; // 24

用户代码通常不需要(也不应该)访问标头元信息,这主要是针对 GC 本身的。但是,我们在此将其公开为用于教育目的的公共方法。

好的,现在当我们有能力创建具有可追溯元信息的对象时,让我们创建我们的工作图。

Object graph

这是对象图的示例,我们将在进一步分析中使用它:

/*
 
   Graph:
 
     A        -- Root
    / \
   B   C
      / \
     D   E
        / \
       F   G
            \
             H
 
*/
 
Node *createGraph() {
   
   
  auto H = new Node{
   
   .name = 'H'};
 
  auto G = new Node{
   
   .name = 'G', .right = H};
  auto F = new Node{
   
   .name = 'F'};
 
  auto E = new Node{
   
   .name = 'E', .left = F, .right = G};
  auto D = new Node{
   
   .name = 'D'};
 
  auto C = 
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值