数据结构中的垃圾回收2024 年 8 月 28 日 | 阅读 10 分钟 垃圾回收 (GC) 是一种动态的内存管理和堆分配技术,它会检查并识别无用的内存块,然后重新分配存储空间以供重用。垃圾回收的主要目标是减少内存泄漏。垃圾回收使程序员无需手动解除分配对象并将它们返回给内存系统。垃圾回收可能占程序总处理时间的很大一部分,因此可能对性能产生重大影响。堆栈分配、区域推断、内存所有权以及各种技术的组合是相关技术的示例。 垃圾回收的基本原理是查找程序中未来无法访问的数据对象,并回收这些对象所使用的资源。 垃圾回收通常不处理内存以外的资源,例如网络套接字、数据库句柄、用户交互窗口、文件和设备描述符。管理这些资源的 方法,尤其是析构函数,可能足以在不需要 GC 的情况下管理内存。在某些 GC 系统中,其他资源可以与内存区域关联,当这些内存区域被收集时,会导致回收这些资源的任务。 许多编程语言,如 RPL、Java、C#、Go 和大多数脚本语言,都需要垃圾回收,无论是作为语言规范的一部分,还是为了实际实现(例如,像 lambda 演算这样的形式语言);这些被称为垃圾回收语言。其他语言,如 C 和 C++,最初是为手动内存管理设计的,但包含了垃圾回收的实现。一些语言,如 Ada、Modula-3 和 C++/CLI,通过使用独立的堆来收集和手动管理的内存,允许在同一个应用程序中进行垃圾回收和手动内存管理;另一些语言,如 D,是垃圾回收的,但允许用户手动删除对象并在需要速度时完全禁用垃圾回收。 垃圾回收通过自动堆分配的动态方法解决了常见且代价高昂的故障,如果这些故障未被发现,可能会导致实际的程序员问题。 分配错误代价高昂,因为它们难以检测和纠正。因此,许多程序员认为垃圾回收是一项必不可少的功能,它通过减少手动堆分配管理来简化程序员的工作。 现在让我们看看一些最著名和最常用的垃圾回收技术。
标记和清除Mark Sweep 算法正如其名一样简单。它包含两个阶段:标记阶段和清除阶段。在标记阶段,收集器会遍历所有根(全局变量、局部变量、堆栈帧、虚拟和硬件寄存器等),并通过在对象周围设置一个位来标记遇到的每个对象。在清除阶段,它还会遍历堆,回收所有未标记对象占用的内存。 下面的 Python 伪代码概述了基本算法。在此示例中,假设收集器是单线程的,尽管可能存在多个变异体。当收集器运行时,所有变异体线程都会被暂停。这种“停止世界”技术可能看起来效率低下,但它极大地简化了收集器的实现,因为变异体无法影响其下方的状态。 代码 从伪代码中可以明显看出,Mark-Sweep 并不会立即识别垃圾。相反,它首先识别所有不是垃圾的对象,例如活动的(活的)对象,然后才将其他所有对象视为垃圾。标记过程是循环的。在检测到活动引用后,我们递归到其子字段,依此类推。由于时间成本和堆栈溢出的风险,递归过程调用并不是标记的合适方法。这就是我们使用显式定义的堆栈的原因。此技术使标记阶段的空间和时间开销都变得显而易见。必须通过对象图追踪的最长路径的长度决定了候选堆栈的最大深度。 理论上,最坏情况等于堆上节点的数量。然而,大多数实际应用程序产生的堆栈相当浅。尽管如此,一个安全的 GC 系统必须处理异常情况。在我们的实现中,我们在将新对象添加到候选对象后立即使用 **mark()**,以控制堆栈大小。标记的问题在于,GC 正是因为内存不足而需要的,但辅助堆栈却需要更多空间。大型应用程序可能会导致垃圾收集器耗尽内存。 有多种方法可以检测溢出。使用显式堆栈的一个优点是,可以立即检测到溢出并启动恢复过程。使用内联检查每次推送是一种简单的方法()。使用保护页并在捕获保护冲突异常后触发恢复可能是稍微更有效的方法。这两种技术的权衡都必须在底层操作系统和硬件的上下文中进行考虑。对于第一种技术,is-full 测试可能只花费几条指令(测试后跟一个分支),但每次检查对象时都会执行。第二种技术需要捕获访问冲突异常,这通常代价高昂但很少发生。 **Sweep()** 是一个具有简单实现的简单函数。它线性遍历堆,释放所有未标记的对象。由于这个原因,我们的堆布局确实会面临解析限制。next object(address) 实现必须能够返回堆中的下一个对象。在大多数情况下,堆只需要能够以一种方式解析。在大多数启用了 GC 的语言运行时中,对象的内存通常会带有对象头。该头提供了有关对象的信息,例如类型、大小、哈希码、标记位、同步块等。 对象头通常放置在对象数据之前。因此,对象的引用指向的是分配的堆单元的中间,紧随对象头之后,而不是第一个字节。这使得从上到下解析堆变得更加容易。在大多数情况下,free(address) 会用堆解析算法识别的预定填充模式填充释放的单元。 Mark and Sweep 算法的优点
引用计数引用计数方法非常简单。它基于计算每个分配的对象有多少指针引用。它是一种简单、本质上是增量的解决方案,因为程序内存管理的开销是分布式的。除了内存管理之外,引用计数还广泛用于操作系统中,作为一种资源管理工具,用于管理文件、套接字等系统资源。 在引用计数技术中,每个分配的对象都有一个引用计数字段。内存管理器负责确保每个对象的引用计数始终等于指向该对象的直接指针引用的数量。下面是该算法的简化版本。 代码 无法回收循环存储是引用计数最主要的缺点。像双向链表和非基本图这样的循环数据结构无法使用简单的引用计数技术成功回收,并且会泄漏内存。 引用计数的优点
结论因此,在本文中,我们了解了数据结构中的垃圾回收以及垃圾回收对于使各种数据结构更高效的重要性。我们还了解了两种主要的垃圾回收算法,即 Mark and Sweep 和 Reference Counting,以及这两种算法的工作原理,以及上述这些垃圾回收算法的显著优点。 下一主题双向链表上的归并排序 |
我们请求您订阅我们的新闻通讯以获取最新更新。