Un artículo para entender el algoritmo de conteo de referencias en el algoritmo de recolección de basura

¡Acostúmbrate a escribir juntos! Este es el octavo día de mi participación en el "Nuggets Daily New Plan·Desafío de actualización de abril",Haga clic para ver los detalles del evento

Este artículo presentará brevemente un algoritmo de reciclaje básico: el algoritmo de conteo de referencias [Collins, 1960], el nombre en inglés de conteo de referencias.

El método de conteo de referencia es muy simple. La capacidad de supervivencia de un objeto se puede determinar directamente mediante la creación y eliminación de relaciones de referencia, por lo que no es necesario recorrer el montón para encontrar todos los objetos supervivientes y luego determinar de forma inversa los objetos basura que no se han recorrido.

Los algoritmos de conteo de referencias se basan en la idea de contar el número de referencias de puntero a cada objeto asignado. Este es un enfoque sencillo, y también es naturalmente incremental, ya que asigna la sobrecarga de administración de memoria en todo el programa.

El algoritmo se basa en un invariante muy simple: es probable que un objeto esté vivo si y solo si el número de referencias a él es mayor que 0 .

Entonces, ¿cómo funciona el algoritmo?

¿Cómo funciona el algoritmo de conteo de referencias?

Bajo el método de conteo de referencia, cada objeto asignado contiene un campo de conteo de referencia.

El administrador de memoria es responsable de mantener invariantes, es decir, el recuento de referencias de cada objeto siempre es igual al número de referencias de puntero directo a ese objeto, y aumenta o disminuye cuando se crea o elimina una referencia a un objeto.

La versión básica del algoritmo se da a continuación:

  • nuevo método: utilizado para crear un objeto, new() asigna un nuevo objeto. Por brevedad, ignoramos los tipos de objetos, asumiendo que todos los objetos son del mismo tipo y tamaño.
  • método de eliminación: implementa la reducción del recuento de referencias y se llama cuando el programa cliente ya no necesita el objetodelete()
  • método de actualización: update() es la única forma de realizar la asignación de punteros en el sistema. Al implementar el incremento del recuento de objetos de referencia, lo incrementamos antes de eliminarlo, lo que maneja correctamente source == targetel caso de .
def new():
	obj = allocate_memory()
	obj.set_reference_count(1)
	return obj

def delete(obj):
	obj.decrement_reference_count()
	if obj.get_reference_count() == 0:
		for child in children(obj):
			delete(child)
		release_memory(obj)

def update(source, target):
	target.increment_reference_count()
	delete(source)
	source = target
复制代码

no se puede resolver la referencia circular

Sin duda, la mayor desventaja del conteo de referencia es que no puede recuperar el almacenamiento circular. Bajo el método de conteo de referencia simple, las estructuras de datos cíclicos, como listas doblemente enlazadas o gráficos no simples, no se pueden recuperar de manera eficiente y perderán memoria. El siguiente ejemplo muestra el problema:

在 delete(A) 和 delete(C) 之后,我们最终得到了一个对象子图的不可访问但连接的组件,该组件无法从任何根访问,但由于非零引用,我们无法回收其节点。

幸运的是,所有其他垃圾收集技术(标记扫描、标记压缩、复制等)都可以轻松处理循环结构。这就是为什么使用引用计数作为主要垃圾收集机制的系统在堆耗尽后利用跟踪收集算法的情况并不少见。

引用计数算法的优缺点

引用计数的内存管理开销分摊在程序运行过程中,同时一旦某个对象成为垃圾对象就可以得到立刻回收。

而且该算法直接操作指针的来源与目标,因此其局部性不会比它所服务的应用程序差,且通常优于需要跟踪所有活动对象的跟踪 GC。该算法的优点如下:

  • 响应性: 内存管理开销分布在整个程序中,与跟踪收集器相比,它通常会导致系统更加流畅和响应迅速。请注意,处理开销与最后一个指针指向的子图的大小相关,并且在某些情况下可能并不重要。
  • 立即内存重用: 与跟踪收集器不同,在收集器执行之前,无法访问的内存保持未分配状态(通常在堆耗尽时);引用计数方法允许立即重新使用丢弃的内存。这种立即重用可为缓存带来更好的时间局部性,从而减少页面错误。它还简化了资源清理,因为可以立即调用终结器,从而更快地释放系统资源。立即重用空间还可以进行优化,例如数据结构的就地更新。
  • 易于实现: 就实现细节而言,基于引用计数的收集是最简单的垃圾回收机制。如果语言运行时不允许指针操作和/或程序员无法确定/操作对象根,则实现特别容易。
  • 控制 vs 正确性:引用计数系统可以为程序员提供对对象分配和解除分配的完全控制。它可以允许程序员在其认为安全的地方优化引用计数开销。这确实带来了正确性挑战,并且需要更高的编码纪律。即使没有巧妙的优化,客户端程序的接口和引用计数方案之间也存在紧密耦合。它要求客户端正确调用增加/减少引用计数的操作。
  • 空间开销: 每个对象承载引用计数字段的空间开销。理论上,对于非常小的对象,这可能相当于 50% 的开销。这种开销需要与内存单元的立即重用以及引用计数在收集期间不依赖于堆空间的事实相权衡。引用计数系统可以通过使用单个字节进行引用计数而不是使用全字来减少空间开销。这样的系统通过回退跟踪方案(如标记扫描)来增加引用计数,以收集具有最大引用计数(和循环引用)的对象。

缺点如下:

  • 指针更新开销: 与指针更新是免费的跟踪方案不同,引用计数会带来很大的开销,因为每次指针更新都需要更新两个引用计数以保持程序的正确性。
  • 原子化操作: 为了避免多线程竞争可能导致的对象释放过早,引用计数的增减操作记忆加载和存储指针的操作都必须是原子化的,而原子化的操作就需要解决很多线程竞争问题。
  • 循环结构: 正如我们之前所讨论的,引用计数的最大缺点是它无法回收循环存储。在简单引用计数方法下,双向链表或非简单图等循环数据结构无法有效回收,并且会泄漏内存。

最坏情况下,某一个对象的引用计数可能等于堆中对象的总数,就导致引用计数所占的空间必须和某一个指针域大小相同,这一空间也会非常昂贵。

最后,引用计数算法仍有可能停顿的出现。当删除某一个大型结构根节点的最后一个引用时,该算法会递归的删除根节点的每一个子孙节点,线程安全的引用计数回收所导致的最大停顿时间可能会比追踪式回收器的长。

总结

引用计数就实现细节来说,是最简单的垃圾回收机制,因此在众多系统中得到广泛应用,包括如 Lisp、Awk、Perl 和 Python 等编程语言、部分应用程序如 Photoshop、Real Network的 Rhapsody 音乐服务,打印、扫描及文档管理系统)。

Además de la gestión de la memoria , el recuento de referencias también se utiliza ampliamente como mecanismo de gestión de recursos en los sistemas operativos para gestionar los recursos del sistema, como archivos y sockets.

Para dos problemas con algoritmos de conteo de referencia: la sobrecarga de las operaciones de conteo de referencia y el problema de referencia circular de estructuras cíclicas , hay muchas formas de mejorar. Este punto se introducirá en el próximo artículo, gracias por poder verlo aquí.

Supongo que te gusta

Origin juejin.im/post/7085003963279343653
Recomendado
Clasificación