2 .. Introducción a la importancia de la recolección de basura y sus
3 principales técnicas, con sus ventajas y desventajas. También se da
4 un breve recorrido sobre el estado del arte.
10 ============================================================================
14 ----------------------------------------------------------------------------
16 *Recolección de basura* se refiere a la recuperación automática de memoria
17 del *heap* [#gcheap]_ una vez que el programa ha dejado de hacer referencia
18 a ella (y por lo tanto, ha dejado de utilizarla).
20 .. [#gcheap] *Heap* es un área de memoria que se caracteriza por ser
21 dinámica (a diferencia del área de memoria estática que está disponible
22 durante toda la ejecución de un programa). Un programa puede reservar
23 memoria en tiempo de ejecución según sea necesario y liberarla cuando ya
24 no la necesita. A diferencia del *stack*, la duración de la *reserva* no
25 está atada a un bloque de código.
27 A medida que el tiempo pasa, cada vez los programas son más complejos y es
28 más compleja la administración de memoria. Uno de los aspectos más
29 importantes de un recolector de basura es lograr un mayor nivel de
30 abstracción y modularidad, dos conceptos claves en la ingeniería de
31 software [JOLI96]_. En particular, al diseñar o programar bibliotecas, de
32 no haber un recolector de basura, **la administración de memoria pasa a ser
33 parte de la interfaz**, lo que produce que los módulos tengan un mayor
34 grado de acoplamiento.
36 Además hay una incontable cantidad de problemas asociados al manejo
37 explícito de memoria que simplemente dejan de existir al utilizar un
38 recolector de basura. Por ejemplo, los errores en el manejo de memoria
39 (como *buffer overflows* [#gcbuff]_ o *dangling pointers* [#gcdang]_) son
40 la causa más frecuente de problemas de seguridad [BEZO06]_.
42 .. [#gcbuff] Un *buffer overflow* (*desbordamiento de memoria* en
43 castellano) se produce cuando se copia un dato a un área de memoria que
44 no es lo suficientemente grande para contenerlo. Esto puede producir que
45 el programa sea abortado por una violación de segmento, o peor,
46 sobreescribir un área de memoria válida, en cuyo caso los resultados son
49 .. [#gcdang] Un *dangling pointer* (*puntero colgante* en castellano) es un
50 puntero que apunta a un área de memoria inválida. Ya sea porque el
51 elemento apuntado no es el mismo tipo o porque la memoria ya ha sido
52 liberada. Al ser desreferenciado, los resultados son impredecibles, el
53 programa podría abortarse por una violación de segmento o podrían pasar
54 peores cosas si el área de memoria fue realocada para almacenar otro
57 La recolección de basura nació junto a Lisp_ a finales de 1950 y en los
58 siguientes años estuvo asociada principalmente a lenguajes funcionales,
59 pero en la actualidad está presente en prácticamente todos los lenguajes de
60 programación, de alto o bajo nivel, aunque sea de forma opcional. En los
61 últimos 10 años tuvo un gran avance, por la adopción en lenguajes de
62 desarrollo rápido utilizados mucho en el sector empresarial, en especial
63 Java_, que fue una plataforma de facto para la investigación y desarrollo
64 de recolectores de basura (aunque no se limitaron a este lenguaje las
67 En las primeras implementaciones de recolectores de basura la penalización
68 en la eficiencia del programa se volvía prohibitiva para muchas
69 aplicaciones. Es por esto que hubo bastante resistencia a la utilización de
70 recolectores de basura, pero el avance en la investigación fue haciendo que
71 cada vez sea una alternativa más viable al manejo manual de memoria,
72 incluso para apliaciones con altos requerimientos de eficiencia. En la
73 actualidad un programa que utiliza un recolector moderno puede ser
74 comparable en eficiencia con uno que utiliza un esquema manual. En
75 particular, si el programa fue diseñado con el recolector de basura en
76 mente en ciertas circunstancias puede ser incluso más eficiente que uno que
77 hace manejo explícito de la memoria. Muchos recolectores mejoran la
78 localidad de referencia, haciendo que el programa tenga un mejor
79 comportamiento con el caché y la memoria virtual.
81 El recolector de basura debe tener un comportamiento correcto y predecible
82 para que sea útil, si el programador no puede confiar en el recolector
83 de basura, éste se vuelve más un problema que una solución, porque
84 introduce nuevos puntos de falla en los programas, y lo que es peor,
85 puntos de falla no controlados por el programador, volviendo mucho más
86 difícil la búsqueda de errores.
92 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
94 Los programas pueden hacer uso principalmente de 4 áreas de memoria:
97 Se trata de la memoria más básica de una computadora. Es el área de
98 memoria en la que puede operar realmente el procesador, es extremadamente
99 escasa y generalmente su uso es administrado por el lenguaje de
100 programación (o compilador más específicamente). Excepto en situaciones
101 muy particulares, realizando tareas de muy bajo nivel, un programador
102 nunca manipula los registros explícitamente.
104 Área de memoria estática:
105 Es la forma de memoria más simple que un programador utiliza
106 explícitamente. En general las variables globales se almacenan en este
107 área, que es parte inherente del programa y está disponible durante toda
108 su ejecución, por lo tanto nunca cambia su capacidad en tiempo de
109 ejecución. Es la forma más básica de administrar memoria, pero tiene una
110 limitación fundamental: **el tamaño de la memoria tiene que ser conocido
111 en tiempo de compilación**. Los primeros lenguajes de programación solo
112 contaban con este tipo de memoria (además de los registros del
116 Los primeros lenguajes de programación que hicieron uso de una pila
117 aparecieron en el año 1958 (Algol-58 y Atlas Autocode) y fueron los
118 primeros en introducir estructura de bloques, almacenando las
119 variables locales a estos bloques utilizando una pila [JOLI96]_.
120 Esto permite utilizar recursividad y tener un esquema simple de memoria
121 dinámica. Sin embargo este esquema es muy limitado porque el orden de
122 reserva y liberación de memoria tiene que estar bien establecido. Una
123 celda [#gccelda]_ alocada antes que otra nunca puede ser liberada antes
126 .. [#gccelda] En general en la literatura se nombra a una porción de
127 memoria alocada individualmente *celda*, *nodo* u *objeto*
128 indistintamente. En este trabajo se utilizará la misma nomenclatura
129 (haciendo mención explícita cuando alguno de estos términos se
130 refiera a otra cosa, como al nodo de una lista o a un objeto en el
131 sentido de programación orientada a objetos).
134 A diferencia del *stack*, el *heap* provee un área de memoria que puede
135 ser obtenida dinámicamente pero sin limitaciones de orden. Es el tipo de
136 memoria más flexible y por lo tanto el más complejo de administrar; razón
137 por la cual existen los recolectores de basura.
139 La recolección de basura impone algunas restricciones sobre la manera de
140 utilizar el *heap*. Debido a que un recolector de basura debe ser capaz de
141 determinar el grafo de conectividad de la memoria en uso, es necesario que
142 el programa siempre tenga alguna referencia a las celdas activas en los
143 registros, memoria estática o *stack* (normalmente denominado *root set*).
145 Esto implica que una celda sea considerada basura si y sólo si no puede ser
146 alcanzada a través del grafo de conectividad que se comienza a recorrer
147 desde el *root set*. Por lo tanto, una celda está *viva* si y sólo si su
148 dirección de memoria está almacenada en una celda *raíz* (parte del *root
149 set*) o si está almacenada en otra celda *viva* del *heap*.
151 Expresado más formalmente, dada la relación :math:`M \to N`, donde
152 :math:`M` es una celda del *heap* o parte del *root set* y :math:`N` es una
153 celda del *heap*, definida como:
157 M \to N \Longleftrightarrow M \text{ almacena un puntero a } N
159 El conjunto de celdas vivas (o *live set*) queda determinado por:
163 vivas = \left\lbrace N \in Celdas \big/
164 ( \exists r \in Raices / r \to N ) \vee (\exists M \in vivas / M \to N )
167 Cabe aclarar que esta es una definición conceptual, asumiendo que el
168 programa siempre limpia una dirección de memoria almacenada en el *root
169 set* o una celda del *heap* cuando la celda a la que apunta no va a ser
170 utilizada nuevamente. Esto no es siempre cierto y los falsos positivos que
171 esto produce se conoce como un tipo de pérdida de memoria (que es posible
172 incluso al utilizar un recolector de basura) llamada pérdida de memoria
173 *lógica*. Esto puede no ser evitable (incluso cuando el programador no
174 cometa errores) en lenguajes de programación que requieran un recolector de
177 Por último, siendo que el recolector de basura es parte del programa de
178 forma indirecta, es común ver en la literatura que se direfencia entre
179 2 partes del programa, el recolector de basura y el programa en sí. Dado
180 que para el recolector de basura, lo único que interesa conocer del
181 programa en sí son los cambios al grafo de conectividad de las celdas,
182 normalmente se lo llama *mutator* (mutador).
187 Recorrido del grafo de conectividad
188 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
190 El problema de encontrar las celdas *vivas* de un programa se reduce a recorrer
191 un grafo dirigido. El grafo se define como:
197 Donde :math:`V` es el conjunto de vértices, dado por las celdas de memoria
198 y :math:`A` es un conjunto de pares ordenados (aristas), dado por la
199 relación :math:`M \rightarrow N` (es decir, los punteros).
201 El grafo comienza a recorrerse desde el *root set* y todos los vértices que
202 fueron visitados componen el *live set*; el resto de los vértices son
205 Más formalmente, Definimos:
208 secuencia de vértices tal que cada uno de los vértices tiene una arista
209 al próximo vértice en la secuencia. Todo camino finito tiene un *vértice
210 inicial* y un *vértice final* (llamados en conjunto *vértices
211 terminales*). Cualquier vértice no terminal es denominado *vértice
216 \underset{v_1 \rightarrow v_N}{C} = \left\lbrace
217 v_1, \dotsc, v_N \in V \big/ \underset{i \in [1,N-1]}{\forall v_i}
218 \exists (v_i \to v_{i+1}) \in A
222 decimos que :math:`M` está *conectado* a :math:`N` si y sólo si existe un
223 camino de :math:`M` a :math:`N`.
227 M \mapsto N \Longleftrightarrow \exists \underset{M \to N}{C} \in G
230 el conjunto de celdas *vivas* está dado por todos los vértices
231 (:math:`v`) del grafo para los cuales existe una raíz en el *root set*
232 que esté conectada a él.
236 Live \thickspace set = \left\lbrace v \in V \big/
237 \left( \exists r \in Root \thickspace set \big/ r \mapsto v \right)
241 la basura, o celdas *muertas*, quedan determinadas entonces por todas las
242 celdas del *heap* que no son parte del *live set*.
246 Basura = V - Live \thickspace set
248 Esto es, efectivamente, una partición del *heap* (ver figura
249 :vref:`fig:gc-heap-parts`).
252 .. fig:: fig:gc-heap-parts
254 Distintas partes de la memoria, incluyendo relación entre *basura*,
255 *live set*, *heap* y *root set*.
262 node [ shape = record, width = 0, height = 0 ];
264 subgraph cluster_heap {
270 subgraph cluster_live {
283 subgraph cluster_garbage {
288 node [ style = filled, fillcolor = white ];
293 subgraph cluster_root {
298 node [ style = filled, fillcolor = gray96 ];
302 r0 -> h1 -> h2 -> h5;
303 r1 -> h5 -> h6 -> h1;
310 Al proceso de visitar los vértices *conectados* desde el *root set* se lo
311 denomina *marcado*, *fase de marcado* o *mark phase* en inglés, debido
312 a que es necesario marcar los vértices para evitar visitar 2 veces el mismo
313 nodo en casos de que el grafo contenga ciclos [#gccycle]_. De forma similar
314 a la búsqueda, que puede realizarse *primero a lo ancho* (*breadth-first*)
315 o *primero a lo alto* (*depth-first*) del grafo, el marcado de un grafo
316 también puede realizarse de ambas maneras. Cada una podrá o no tener
317 efectos en la eficiencia, en particular dependiendo de la aplicación puede
318 convenir uno u otro método para lograr una mejor localidad de referencia
319 y de esta manera tener un mejor comportamiento de la memoria virtual o del
322 .. [#gccycle] Un ciclo es un camino donde el *vértice inicial* es el mismo
323 que el *vértice final*. Por lo tanto, los *vértices terminales* son
324 completamente arbitrarios, ya que cualquier *vértice interior* puede ser
325 un *vértice terminal*.
327 Un algoritmo simple (recursivo) de marcado *primero a lo alto* puede ser
328 el siguiente (asumiendo que partimos con todos los vértices sin marcar)
334 for (src, dst) in v.edges
341 .. [#gcpseudo] Para presentar los algoritmos se utiliza una forma simple de
342 pseudo-código. El pseudo-código se escribe en inglés para que pueda ser
343 más fácilmente contrastado con la literatura, que está en inglés. Para
344 diferenciar posiciones de memoria y punteros de las celdas en sí, se usa
345 la misma sintaxis que C, ``r*`` denota una referencia o puntero y ``*r``
346 denota "objeto al que apunta ``r``\ ". Se sobreentiende que ``r = o``
347 siempre toma la dirección de memoria de ``o``.
349 Una vez concluido el marcado, sabemos que todos los vértices con la marca
350 son parte del *live set* y que todos los vértices no marcados son *basura*.
351 Esto es conocido también como **abstracción bicolor**, dado que en la
352 literatura se habla muchas veces de *colorear* las celdas. En general, una
353 celda sin marcar es de color blanco y una marcada de color negro.
355 Puede observarse un ejemplo del algoritmo en la figura
356 :vref:`fig:gc-mark-1`, en la cual se marca el sub-grafo apuntando por
357 ``r0``. Luego se marca el sub-grafo al que apunta ``r1`` (ver figura
358 :vref:`fig:gc-mark-2`), concluyendo con el marcado del grafo completo,
359 dejando sin marcar solamente las celdas *basura* (en blanco).
362 .. fig:: fig:gc-mark-1
364 Ejemplo de marcado del grafo de conectividad (parte 1).
368 Se comienza a marcar el grafo por la raíz r0.
375 node [ shape = record, width = 0, height = 0];
376 edge [ color = gray40 ];
378 subgraph cluster_all {
381 label = "root\nset|<r0> r0\n*|<r1> r1",
387 node [ style = filled, fillcolor = gray25, fontcolor = white ];
391 root:r0 -> h1 [ style = bold, color = black ];
392 h1 -> h2 -> h5 -> h1;
401 Luego de marcar el nodo ``h1``, se procede al ``h2``.
408 node [ shape = record, width = 0, height = 0 ];
409 edge [ color = gray40 ];
411 subgraph cluster_all {
414 label = "root\nset|<r0> r0\n*|<r1> r1",
420 node [ style = filled, fillcolor = gray25, fontcolor = white ];
424 root:r0 -> h1 [ color = gray10 ];
425 h1 -> h2 [ style = bold, color = black ];
435 Luego sigue el nodo h5.
442 node [ shape = record, width = 0, height = 0 ];
443 edge [ color = gray40 ];
445 subgraph cluster_all {
448 label = "root\nset|<r0> r0\n*|<r1> r1",
454 node [ style = filled, fillcolor = gray25, fontcolor = white ];
458 root:r0 -> h1 [ color = gray10 ];
459 h1 -> h2 [ color = gray10 ];
460 h2 -> h5 [ style = bold, color = black ];
469 .. fig:: fig:gc-mark-2
471 Ejemplo de marcado del grafo de conectividad (parte 2).
475 El nodo h5 tiene una arista al h1, pero el h1 ya fue visitado, por lo
476 tanto no se visita nuevamente.
483 node [ shape = record, width = 0, height = 0 ];
484 edge [ color = gray40 ];
486 subgraph cluster_all {
489 label = "root\nset|<r0> r0\n*|<r1> r1",
495 node [ style = filled, fillcolor = gray25, fontcolor = white ];
499 root:r0 -> h1 [ color = gray10 ];
500 h1 -> h2 [ color = gray10 ];
501 h2 -> h5 [ color = gray10 ];
502 h5 -> h1 [ style = bold, color = black ];
511 Se concluye el marcado del sub-grafo al que conecta r0, se procede
512 a marcar el sub-grafo al que conecta r1, marcando al nodo h6.
519 node [ shape = record, width = 0, height = 0 ];
520 edge [ color = gray40 ];
522 subgraph cluster_all {
525 label = "root\nset|<r0> r0|<r1> r1\n*",
531 node [ style = filled, fillcolor = gray25, fontcolor = white ];
535 root:r0 -> h1 [ color = gray10 ];
536 h1 -> h2 [ color = gray10 ];
537 h2 -> h5 [ color = gray10 ];
538 h5 -> h1 [ color = gray10 ];
539 root:r1 -> h6 [ style = bold, color = black ];
548 El nodo h6 tiene una arista al h2, pero éste ya fue marcado por lo
549 que no se vuelve a visitar. No hay más raíces, se finaliza el marcado
557 node [ shape = record, width = 0, height = 0 ];
558 edge [ color = gray40 ];
560 subgraph cluster_all {
563 label = "root\nset|<r0> r0|<r1> r1\n*",
569 node [ style = filled, fillcolor = gray25, fontcolor = white ];
573 root:r0 -> h1 [ color = gray10 ];
574 h1 -> h2 [ color = gray10 ];
575 h2 -> h5 [ color = gray10 ];
576 h5 -> h1 [ color = gray10 ];
577 root:r1 -> h6 [ color = gray10 ];
578 h6 -> h2 [ style = bold, color = black ];
589 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
591 Muchos algoritmos utilizan tres colores para realizar el marcado. El tercer
592 color, gris generalmente, indica que una celda debe ser visitada. Esto
593 permite algoritmos :ref:`concurrentes <ref_gc_concurrent>`
594 e :ref:`incrementales <ref_gc_inc>`, además de otro tipo de optimizaciones.
595 Entonces, lo que plantea esta abtracción es una nueva partición del heap al
596 momento de marcar, esta vez son 3 porciones: blanca, gris y negra.
598 Al principio todas las celdas se pintan de blanco, excepto el *root set*
599 que se punta de gris. Luego se van obteniendo celdas del conjunto de las
600 grises y se las pinta de negro, pintando sus hijas directas de gris.
602 Una vez que no hay más celdas grises, tenemos la garantía de que las celdas
603 negras serán el *live set* y las celdas blancas *basura*. Esto se debe
604 a que siempre se mantiene esta invariante: **ninguna celda negra apunta
605 directamente a una celda blanca**. Las celdas blancas siempre son apuntadas
606 por celdas blancas o grises. Entonces, siempre que el conjunto de celdas
607 grises sea vacío, no habrán celdas negras conectadas a blancas, siendo las
608 celdas blancas *basura*.
610 El algoritmo básico para marcar con tres colores es el siguiente (asumiendo
611 que todas las celdas parten pintadas de blanco, es decir, el conjunto
612 blanco contiene todas las celdas de memoria y los conjuntos negro y gris
618 while not gray_set.empty()
621 for (src, dst) in v.edges
626 Es simple notar que este algoritmo es naturalmente no recursivo, lo que de
627 por sí ya presenta una ventaja sobre el marcado *bicolor*.
634 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
636 En general todos los algoritmos de recolección de basura utilizan servicios
637 de una capa inferior [#gclowlayer]_ y proveen servicios a una capa superior
640 .. [#gclowlayer] En general estos servicios están provistos directamente
641 por el sistema operativo pero también pueden estar dados por un
642 administrador de memoria de bajo nivel (o *low level allocator* en
645 .. [#gchilayer] En general estos servicios son utilizados directamente por
646 el lenguaje de programación, pero pueden ser utilizados directamente por
647 el usuario del lenguaje si éste interatúa con el recolector, ya sea por
648 algún requerimiento particular o porque el lenguaje no tiene soporte
649 diercto de recolección de basura y el recolector está implementado como
650 una biblioteca de usuario.
652 A continuación se presentan las primitivas en común que utilizan todos los
653 recolectores a lo largo de este documento.
655 Servicios utilizados por el recolector son los siguientes:
657 :math:`alloc() \to cell`:
658 obtiene una nueva celda de memoria. El mecanismo por el cual se obtiene
659 la celda es indistinto para esta sección, puede ser de una lista libre,
660 puede ser de un administrador de memoria de más bajo nivel provisto por
661 el sistema operativo o la biblioteca estándar de C (``malloc()``), etc.
662 Cómo organizar la memoria es un área de investigación completa y si bien
663 está estrechamente relacionada con la recolección de basura, en este
664 trabajo no se prestará particular atención a este aspecto (salvo casos
665 donde el recolector impone una cierta organización de memoria en el *low
666 level allocator*). Por simplicidad también asumiremos (a menos que se
667 indique lo contrario) que las celdas son de tamaño fijo. Esta restricción
668 normalmente puede ser fácilmente relajada (en los recolectores que la
672 libera una celda que ya no va a ser utilizada. La celda liberada debe
673 haber sido obtenida mediante ``alloc()``.
675 Y los servicios básicos proporcionados por el recolector son los
678 :math:`new() \to cell`:
679 obtiene una celda de memoria para ser utilizada por el programa.
681 :math:`update(ref, cell)`:
682 notifica al recolector que la referencia :math:`ref` ahora apunta
683 a :math:`cell`. Visto más formalmente, sería análogo a decir que hubo un
684 cambio en la conectividad del grafo: la arista :math:`src \to old` cambia
685 por :math:`src \to new` (donde :math:`src` es la celda que contiene la
686 referencia :math:`ref`, :math:`old` es la celda a la que apunta la
687 referencia :math:`ref` y :math:`new` es el argumento :math:`cell`). Si
688 :math:`cell` es ``null``, sería análogo a informar que se elimina la
689 arista :math:`src \to old`.
692 este servicio, según el algoritmo, puede ser utilizado para informar un
693 cambio en la conectividad del grafo, la eliminación de una arista
694 (análogo a :math:`update(ref, null)` pero sin proporcionar información
695 sobre la arista a eliminar). Esto es generalmente útil solo en
696 :ref:`conteo de referencias <ref_gc_rc>`. Para otros recolectores puede
697 significar que el usuario asegura que no hay más referencias a esta
698 celda, es decir, análogo a eliminar el conjunto de aristas
699 :math:`\big\lbrace (v, w) \in A , v \in Live \thickspace set , w \in
700 Live \thickspace set \big/ w = cell`.
703 indica al recolector que debe hacer un análisis del grafo de conectividad
704 en busca de *basura*. Generalmente este servicio es invocado por el
705 propio recolector cuando no hay más celdas reciclables.
707 No todos los servicios son implementados por todos los recolectores, pero
708 son lo suficientemente comunes como para describirlos de forma general en
709 esta sección. Algunos son principalmente ideados para uso interno del
710 recolector, aunque en ciertas circunstancias pueden ser utilizados por el
719 ----------------------------------------------------------------------------
721 En la literatura se encuentran normalmente referencias a 3 algoritmos
722 clásicos, que son utilizados generalmente como bloques básicos para
723 construir recolectores más complejos. Se presentan las versiones históricas
724 más simples a fin de facilitar la comprensión conceptual.
730 Conteo de referencias
731 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
733 Se trata del algoritmo más antiguo de todos, implementado por primera vez
734 por `John McCarthy`_ para Lisp_ a finales de 1950. Se trata de un método
735 :ref:`directo <ref_gc_direct>` e :ref:`incremental <ref_gc_inc>` por
736 naturaleza, ya que distribuye la carga de la recolección de basura durante
737 toda la ejecución del programa, cada vez que el *mutator* cambia la
738 conectividad de algún nodo del grafo de conectividad.
740 El método consiste en tener un contador asociado a cada celda que contenga
741 la cantidad de celdas **vivas** que apuntan a ésta. Es decir, es la
742 cardinalidad del conjunto de aristas que tienen por destino a la celda.
743 Formalmente podemos definir el contador :math:`rc(v)` (de *reference
744 counter* en inglés) de la siguiente manera:
750 (v_1, v_2) \in A \big/
751 v_1 \in Live \thickspace set \cup Root \thickspace set
756 El *mutator* entonces debe actualizar este contador cada vez que el grafo
757 de conectividad cambia, es decir, cada vez que se agrega, modifica
758 o elimina una arista del grafo (o visto de una forma más cercana al código,
759 cada vez que se agrega, modifica o elimina un puntero).
761 Esta invariante es fundamental para el conteo de referencias, porque se
762 asume que si el contador es 0 entonces el *mutator* no tiene ninguna
763 referencia a la celda y por lo tanto es *basura*:
767 rc(v) = 0 \Rightarrow v \in Basura
769 Para mantener esta invariante el *mutator*, cada vez que cambia un puntero
770 debe decrementar en 1 el contador de la celda a la que apuntaba
771 antiguamente e incrementar en 1 el contador de la celda a la que apunta
772 luego de la modificación. Esto asegura que la invariante se mantenga
773 durante toda la ejecución del programa. Si al momento de decrementar un
774 contador éste queda en 0, la celda asociada debe liberarse de forma de
775 poder ser reciclada. Esto implica que si esta celda almacena punteros, los
776 contadores de las celdas apuntadas deben ser decrementados también, porque
777 solo deben almacenarse en el contador las aristas del *live set* para
778 mantener la invariante. De esto puede resultar que otros contadores de
779 referencia queden en 0 y más celdas sean liberadas. Por lo tanto,
780 teóricamente la complejidad de eliminar una referencia puede ser
781 :math:`O(\lvert Live \thickspace set \rvert)` en el peor caso.
783 Las primitivas implementadas para este tipo de recolector son las
784 siguientes (acompañadas de una implementación básica)::
794 cell.rc = cell.rc - 1
796 for child* in cell.children
801 cell.rc = cell.rc + 1
806 .. _ref_gc_rc_cycles:
811 .. _ref_gc_rc_example:
816 A continuación se presenta un ejemplo gráfico para facilitar la comprensión
817 del algoritmo. Por simplicidad se asumen celdas de tamaño fijo con dos
818 punteros, ``left`` (``l``) y ``right`` (``r``) y se muestra el contador de
819 referencias abajo del nombre de cada celda. Se parte con una pequeña
820 estructura ya construída y se muestra como opera el algoritmo al eliminar
821 o cambiar una referencia (cambios en la conectividad del grafo). En un
822 comienzo todas las celdas son accesibles desde el *root set* por lo tanto
823 son todas parte del *live set*.
826 .. fig:: fig:gc-rc-rm-1
828 Eliminación de la referencia ``r0`` :math:`\to` ``h1`` (parte 1).
832 Estado inicial del grafo de conectividad.
839 edge [ color = gray40 ];
849 subgraph cluster_all {
852 label = "root\nset|<r0> r0|<r1> r1",
858 h1 [ label = "h1\n1|<l> l|<r> r" ];
859 h2 [ label = "h2\n2|<l> l|<r> r" ];
860 h3 [ label = "h3\n2|<l> l|<r> r" ];
861 h4 [ label = "h4\n1|<l> l|<r> r" ];
862 h5 [ label = "h5\n1|<l> l|<r> r" ];
863 h6 [ label = "h6\n1|<l> l|<r> r" ];
878 Al ejecutarse ``update(r0, null)``, se comienza por visitar la celda
886 edge [ color = gray40 ];
896 subgraph cluster_all {
899 label = "root\nset|<r0> r0\n*|<r1> r1",
905 h1 [ label = "h1\n1|<l> l|<r> r" ];
906 h2 [ label = "h2\n2|<l> l|<r> r" ];
907 h3 [ label = "h3\n2|<l> l|<r> r" ];
908 h4 [ label = "h4\n1|<l> l|<r> r" ];
909 h5 [ label = "h5\n1|<l> l|<r> r" ];
910 h6 [ label = "h6\n1|<l> l|<r> r" ];
912 root:r0 -> h1 [ style = bold, color = black ];
925 Se decrementa el contador de ``h1`` quedando en 0 (pasa a ser
926 *basura*). Se elimina primero ``h1.l`` y luego ``h1.r``.
933 edge [ color = gray40 ];
943 subgraph cluster_all {
946 label = "root\nset|<r0> r0\n*|<r1> r1",
953 node [ fillcolor = white, fontcolor = black ];
957 h1 [ label = "h1\n0|<l> l|<r> r" ];
958 h2 [ label = "h2\n2|<l> l|<r> r" ];
959 h3 [ label = "h3\n2|<l> l|<r> r" ];
960 h4 [ label = "h4\n1|<l> l|<r> r" ];
961 h5 [ label = "h5\n1|<l> l|<r> r" ];
962 h6 [ label = "h6\n1|<l> l|<r> r" ];
964 root:r0 -> h1 [ style = invis ];
966 h1:l -> h2 [ style = bold, color = black ];
976 .. fig:: fig:gc-rc-rm-2
979 Eliminación de la referencia ``r0`` :math:`\to` ``h1`` (parte 2).
983 Se decrementa el contador de ``h2`` pero no queda en 0 (permanece en
991 edge [ color = gray40 ];
1001 subgraph cluster_all {
1004 label = "root\nset|<r0> r0\n*|<r1> r1",
1011 node [ fillcolor = white, fontcolor = black ];
1015 h1 [ label = "h1\n0|<l> l|<r> r" ];
1016 h2 [ label = "h2\n1|<l> l|<r> r" ];
1017 h3 [ label = "h3\n2|<l> l|<r> r" ];
1018 h4 [ label = "h4\n1|<l> l|<r> r" ];
1019 h5 [ label = "h5\n1|<l> l|<r> r" ];
1020 h6 [ label = "h6\n1|<l> l|<r> r" ];
1022 root:r0 -> h1 [ style = invis ];
1024 h1:l -> h2 [ style = invis ];
1025 h1:r -> h3 [ style = bold, color = black ];
1035 El contador de ``h3`` tampoco queda en 0, sigue en el *live set*.
1042 edge [ color = gray40 ];
1052 subgraph cluster_all {
1055 label = "root\nset|<r0> r0|<r1> r1",
1062 node [ fillcolor = white, fontcolor = black ];
1066 h1 [ label = "h1\n0|<l> l|<r> r" ];
1067 h2 [ label = "h2\n1|<l> l|<r> r" ];
1068 h3 [ label = "h3\n1|<l> l|<r> r" ];
1069 h4 [ label = "h4\n1|<l> l|<r> r" ];
1070 h5 [ label = "h5\n1|<l> l|<r> r" ];
1071 h6 [ label = "h6\n1|<l> l|<r> r" ];
1073 root:r0 -> h1 [ style = invis ];
1075 h1:l -> h2 [ style = invis ];
1076 h1:r -> h3 [ style = invis ];
1085 Se comienza por eliminar la referencia de ``r0`` a ``h1``, que determina
1086 que ``h1`` se convirtió en *basura* (ver figura :vref:`fig:gc-rc-rm-1`). Esto
1087 conduce al decremento del contador de ``h2`` y ``h3`` que permanecen en el
1088 *live set* ya que sus contadores siguen siendo mayores a 0 (ver figura
1089 :vref:`fig:gc-rc-rm-2`).
1092 .. fig:: fig:gc-rc-up-1
1094 Cambio en la referencia ``h2.l`` :math:`\to` ``h2`` a ``h2.l``
1095 :math:`\to` ``h5`` (parte 1).
1099 Comienza ``update(h3.l, h5)``, se incrementa el contador de ``h5``.
1106 edge [ color = gray40 ];
1116 subgraph cluster_all {
1119 label = "root\nset|<r0> r0|<r1> r1",
1126 node [ fillcolor = white, fontcolor = black ];
1130 h1 [ label = "h1\n0|<l> l|<r> r" ];
1131 h2 [ label = "h2\n1|<l> l|<r> r" ];
1132 h3 [ label = "h3\n1|<l> l\n*|<r> r" ];
1133 h4 [ label = "h4\n1|<l> l|<r> r" ];
1134 h5 [ label = "h5\n2|<l> l|<r> r" ];
1135 h6 [ label = "h6\n1|<l> l|<r> r" ];
1137 root:r0 -> h1 [ style = invis ];
1138 h1:l -> h2 [ style = invis ];
1139 h1:r -> h3 [ style = invis ];
1144 h3:l -> h5 [ style = dotted, color = black ];
1151 Luego se procede a visitar las hijas de ``h3``, comenzando por ``h2``.
1158 edge [ color = gray40 ];
1168 subgraph cluster_all {
1171 label = "root\nset|<r0> r0|<r1> r1",
1178 node [ fillcolor = white, fontcolor = black ];
1182 h1 [ label = "h1\n0|<l> l|<r> r" ];
1183 h2 [ label = "h2\n1|<l> l|<r> r" ];
1184 h3 [ label = "h3\n1|<l> l\n*|<r> r" ];
1185 h4 [ label = "h4\n1|<l> l|<r> r" ];
1186 h5 [ label = "h5\n2|<l> l|<r> r" ];
1187 h6 [ label = "h6\n1|<l> l|<r> r" ];
1189 root:r0 -> h1 [ style = invis ];
1190 h1:l -> h2 [ style = invis ];
1191 h1:r -> h3 [ style = invis ];
1195 h3:l -> h2 [ style = bold, color = black ];
1196 h3:l -> h5 [ style = dotted, color = black ];
1203 Se decrementa el contador de ``h2`` y queda en 0 (pasa a ser
1204 *basura*). Se eliminan las referencias a las hijas.
1211 edge [ color = gray40 ];
1221 subgraph cluster_all {
1224 label = "root\nset|<r0> r0|<r1> r1",
1231 node [ fillcolor = white, fontcolor = black ];
1235 h1 [ label = "h1\n0|<l> l|<r> r" ];
1236 h2 [ label = "h2\n1|<l> l|<r> r" ];
1237 h3 [ label = "h3\n1|<l> l\n*|<r> r" ];
1238 h4 [ label = "h4\n1|<l> l|<r> r" ];
1239 h5 [ label = "h5\n2|<l> l|<r> r" ];
1240 h6 [ label = "h6\n1|<l> l|<r> r" ];
1242 root:r0 -> h1 [ style = invis ];
1243 h1:l -> h2 [ style = invis ];
1244 h1:r -> h3 [ style = invis ];
1246 h2:l -> h4 [ style = bold, color = black ];
1248 h3:l -> h2 [ style = invis ];
1249 h3:l -> h5 [ style = dotted, color = black ];
1255 .. fig:: fig:gc-rc-up-2
1257 Cambio en la referencia ``h2.l`` :math:`\to` ``h2`` a ``h2.l``
1258 :math:`\to` ``h5`` (parte 2).
1262 Se decrementa el contador de ``h4`` quedando en 0, pasa a ser
1263 *basura*. Se continúa con ``h5``.
1270 edge [ color = gray40 ];
1280 subgraph cluster_all {
1283 label = "root\nset|<r0> r0|<r1> r1",
1290 node [ fillcolor = white, fontcolor = black ];
1294 h1 [ label = "h1\n0|<l> l|<r> r" ];
1295 h2 [ label = "h2\n1|<l> l|<r> r" ];
1296 h3 [ label = "h3\n1|<l> l\n*|<r> r" ];
1297 h4 [ label = "h4\n0|<l> l|<r> r" ];
1298 h5 [ label = "h5\n2|<l> l|<r> r" ];
1299 h6 [ label = "h6\n1|<l> l|<r> r" ];
1301 root:r0 -> h1 [ style = invis ];
1302 h1:l -> h2 [ style = invis ];
1303 h1:r -> h3 [ style = invis ];
1305 h2:l -> h4 [ style = invis ];
1306 h2:r -> h5 [ style = bold, color = black ];
1307 h3:l -> h2 [ style = invis ];
1308 h3:l -> h5 [ style = dotted, color = black ];
1315 Se decrementa el contador de ``h5`` pero sigue siendo mayor que 0.
1322 edge [ color = gray40 ];
1332 subgraph cluster_all {
1335 label = "root\nset|<r0> r0|<r1> r1",
1342 node [ fillcolor = white, fontcolor = black ];
1346 h1 [ label = "h1\n0|<l> l|<r> r" ];
1347 h2 [ label = "h2\n1|<l> l|<r> r" ];
1348 h3 [ label = "h3\n1|<l> l\n*|<r> r" ];
1349 h4 [ label = "h4\n0|<l> l|<r> r" ];
1350 h5 [ label = "h5\n1|<l> l|<r> r" ];
1351 h6 [ label = "h6\n1|<l> l|<r> r" ];
1353 root:r0 -> h1 [ style = invis ];
1354 h1:l -> h2 [ style = invis ];
1355 h1:r -> h3 [ style = invis ];
1357 h2:l -> h4 [ style = invis ];
1358 h2:r -> h5 [ style = invis ];
1359 h3:l -> h5 [ style = bold, color = black ];
1360 h3:l -> h2 [ style = invis ];
1367 Se termina por actualizar la referencia de ``h3.l`` para que apunte
1375 edge [ color = gray40 ];
1385 subgraph cluster_all {
1388 label = "root\nset|<r0> r0|<r1> r1",
1395 node [ fillcolor = white, fontcolor = black ];
1399 h1 [ label = "h1\n0|<l> l|<r> r" ];
1400 h1 [ label = "h1\n0|<l> l|<r> r" ];
1401 h2 [ label = "h2\n0|<l> l|<r> r" ];
1402 h3 [ label = "h3\n1|<l> l|<r> r" ];
1403 h4 [ label = "h4\n0|<l> l|<r> r" ];
1404 h5 [ label = "h5\n1|<l> l|<r> r" ];
1405 h6 [ label = "h6\n1|<l> l|<r> r" ];
1407 root:r0 -> h1 [ style = invis ];
1408 h1:l -> h2 [ style = invis ];
1409 h1:r -> h3 [ style = invis ];
1411 h2:l -> h4 [ style = invis ];
1412 h2:r -> h5 [ style = invis ];
1414 h3:l -> h2 [ style = invis ];
1420 Luego se cambia una referencia (en vez de eliminarse) realizándose la
1421 operación ``update(h3.l, h5)``. Para esto primero se incrementa el contador
1422 de referencias de ``h5`` para evitar confundirlo accidentalmente con
1423 *basura* si se elimina alguna celda que apuntaba a ésta. Luego se procede
1424 a decrementar el contador de ``h2`` que queda en 0, transformándose en
1425 *basura* (ver figura :vref:`fig:gc-rc-up-1`). Lo mismo pasa cuando se
1426 desciende a ``h4``, pero al descender a ``h5`` y decrementar el contador,
1427 éste sigue siendo mayor que 0 (pues ``h3`` va a apuntar a ``h5``) así que
1428 permanece en el *live set*. Finalmente se termina de actualizar la
1429 referencia ``h3.l`` para que apunte a ``h5`` (ver figura
1430 :vref:`fig:gc-rc-up-2`).
1434 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1441 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1450 ----------------------------------------------------------------------------
1452 .. explicar la cantidad de cosas que hay (que son muchas) y dar algunos
1458 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1460 La cantidad de clasificaciones que pueden hacerse respecto a algoritmos de
1461 recolección de basura son muy grandes. Muchas de estas clasificaciones se
1462 superponen entre sí, habiendo algoritmos que aplican muchas técnicas a la
1463 vez o recolectores híbridos que aplican una técnica para algún tipo de
1464 objeto y otra para otros.
1466 A continuación se enumeran las clasificaciones más importantes que se
1467 pudieron observar en la investigación sobre el `estado del arte`_.
1474 Generalmente se llama recolección **directa** a aquella en la cual el
1475 compilador o lenguaje instrumenta al *mutator* de forma tal que la
1476 información de conectividad se mantenga activamente cada vez que hay un
1477 cambio en él. Normalmente se utiliza un contador de referencia en cada
1478 celda para este propósito, permitiendo almacenar en todo momento la
1479 cantidad de nodos que apuntan a ésta (dejando de lado el :ref:`problema de
1480 los ciclos <ref_gc_rc_cycles>`). Esto permite reclamar una celda
1481 instantáneamente cuando el *mutator* deja de hacer referencia a ella. Este
1482 tipo de recolectores son, inherentemente :ref:`incrementales <ref_gc_inc>`.
1484 Por el contrario, los recolectores **indirectos** normalmente no
1485 interfieren con el *mutator* en cada actualización del grafo de
1486 conectividad (exceptuando algunos :ref:`recolectores incrementales
1487 <ref_gc_inc>` que a veces necesitan instrumentar el *mutator* pero no para
1488 mantener el estado del grafo de conectividad completo). La recolección se
1489 dispara usualmente cuando el *mutator* requiere alocar memoria pero no hay
1490 más memoria libre conocida disponible y el recolector se encarga de generar
1491 la información de conectividad desde cero para determinar qué celdas son
1494 Estas son las dos grandes familias de recolectores, también conocidos como
1495 `conteo de referencias`_ (directa) y *traicing garbage collection*
1504 Recolección incremental es aquella que se realiza de forma intercalada con
1505 el *mutator*. En general el propósito es disminuir el tiempo de las pausas
1506 causadas por el recolector (aunque generalmente el resultado es un mayor
1507 costo en tiempo total de recolección).
1509 De los `algoritmos clásicos`_ el único que es incremental en su forma más
1510 básica es el `conteo de referencias`_. Otros recolectores pueden hacerse
1511 incrementales de diversas maneras, pero en general consta de hacer parte
1512 del trabajo de escanear el grafo de conectividad cada vez que el *mutator*
1513 aloca memoria. En general para hacer esto es también necesario instrumentar
1514 al *mutator* de forma tal que informe al recolector cada vez que cambia el
1515 grafo de conectividad, para que éste pueda marcar al sub-grafo afectado por
1516 el cambio como *desactualizado* y así re-escanearlo nuevamente en la
1519 En general la eficiencia de los recolectores incrementales disminuye
1520 considerablemente cuando el *mutator* actualiza muy seguido el grafo de
1521 conectividad, porque debe re-escanear sub-grafos que ya había escaneado una
1522 y otra vez. A esto se debe también que en general el tiempo de
1523 procesamiento total de un recolector incremental sea mayor que uno no
1524 incremental, aunque el tiempo de pausa de una recolección sea menor.
1527 .. _ref_gc_concurrent:
1529 Concurrente / *stop-the-world*
1530 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1532 Los recolectores concurrentes son aquellos que pueden correr en paralelo
1533 con el *mutator*. Por el contrario, aquellos que pausan el *mutator* para
1534 realizar la recolección son usualmente denominados *stop-the-world* (*parar
1535 el mundo*), haciendo referencia a que pausan todos los hilos del *mutator*
1536 para poder escanear el grafo de conectividad de forma consistente.
1538 Para lograr que un recolector sea concurrente generalmente el mecanismo es
1539 similar al necesario para hacer un :ref:`recolector incremental
1540 <ref_gc_inc>`: hay que instrumentar al *mutator* para que informe al
1541 recolector cuando se realiza algún cambio en el grafo de conectividad, de
1542 forma tal que pueda volver a escanear el sub-grafo afectado por el cambio.
1544 Esto también trae como consecuencia el incremento en el tiempo total que
1545 consume el recolector, debido a la necesidad de re-escanear sub-grafos que
1546 han sido modificados.
1552 1. Instrumentar el *mutator* de forma tal de que informe al recolector cada
1553 vez que cambia el grafo de conectividad, de manera que pueda re-escanear
1554 el sub-grafo que afectado por el cambio.
1556 2. Tomar una foto de la memoria y escanear esa foto estática en vez de la
1557 memoria original, de forma que cualquier cambio en del *mutator* no
1558 afecte la vista de la memoria del recolector.
1560 Ambos métodos tienen ventajas y desventajas. El primero no necesita hacer
1561 una copia costosa de la memoria, pero existe la posibilidad de que el
1562 recolector nunca pueda escanear el grafo de conectividad completo, si el
1563 *mutator* modifica el grafo más rápido de lo que el recolector lo escanea.
1564 Si bien hay formas de evitar esto, es usual que el trabajo de recolección
1565 se vea considerablemente incrementado por la cantidad de veces que hay que
1566 re-escanear el grafo. El segundo método puede ser costoso por las copias
1567 necesarias (aunque esto puede ser mitigado empleando alguna forma de *COW*
1568 [#gccow]_) y existe la posibilidad de que no se recuperen celdas que el
1572 .. [#gccow] *COW* (del inglés *Copy On Write* o *copiar al escribir*) es
1573 una técnica muy utilizada para disminuir las copias innecesarias,
1574 evitando hacer la copia hasta que haya una modificación. Mientras no
1575 hayan modificaciones no es necesario realizar una copia porque todos los
1576 interesados verán la misma información. Esta técnica es utilizada por el
1577 *kernel* de Linux_ por ejemplo, para que varias instancias de un mismo
1578 proceso puedan compartir la memoria.
1581 Boehm, H., and M. Weiser, "Garbage Collection in an Uncooperative
1582 Environment", Software Practice & Experience, September 1988, pp. 807-820.
1583 http://www.hpl.hp.com/personal/Hans_Boehm/spe_gc_paper/preprint.pdf
1584 (http://www.hpl.hp.com/personal/Hans_Boehm/spe_gc_paper/)
1586 Boehm, H., "Space Efficient Conservative Garbage Collection", Proceedings
1587 of the ACM SIGPLAN '93 Conference on Programming Language Design and
1588 Implementation, SIGPLAN Notices 28, 6 (June 1993), pp. 197-206.
1589 http://www.hpl.hp.com/personal/Hans_Boehm/gc/papers/pldi93.ps.Z
1591 Boehm, H., A. Demers, and S. Shenker, "Mostly Parallel Garbage Collection",
1592 Proceedings of the ACM SIGPLAN '91 Conference on Programming Language
1593 Design and Implementation, SIGPLAN Notices 26, 6 (June 1991), pp. 157-164.
1594 http://www.hpl.hp.com/personal/Hans_Boehm/gc/papers/pldi91.ps.Z
1596 Implementación de Bohem GC
1597 http://www.hpl.hp.com/personal/Hans_Boehm/gc/gcdescr.html
1599 Interfaz de Bohem GC
1600 http://www.hpl.hp.com/personal/Hans_Boehm/gc/gcinterface.html
1603 .. include:: links.rst
1605 .. vim: set ts=3 sts=3 sw=3 et tw=78 :