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
978 Eliminación de la referencia ``r0`` :math:`\to` ``h1`` (parte 2).
982 Se decrementa el contador de ``h2`` pero no queda en 0 (permanece en
990 edge [ color = gray40 ];
1000 subgraph cluster_all {
1003 label = "root\nset|<r0> r0\n*|<r1> r1",
1010 node [ fillcolor = white, fontcolor = black ];
1014 h1 [ label = "h1\n0|<l> l|<r> r" ];
1015 h2 [ label = "h2\n1|<l> l|<r> r" ];
1016 h3 [ label = "h3\n2|<l> l|<r> r" ];
1017 h4 [ label = "h4\n1|<l> l|<r> r" ];
1018 h5 [ label = "h5\n1|<l> l|<r> r" ];
1019 h6 [ label = "h6\n1|<l> l|<r> r" ];
1021 root:r0 -> h1 [ style = invis ];
1023 h1:l -> h2 [ style = invis ];
1024 h1:r -> h3 [ style = bold, color = black ];
1034 El contador de ``h3`` tampoco queda en 0, sigue en el *live set*.
1041 edge [ color = gray40 ];
1051 subgraph cluster_all {
1054 label = "root\nset|<r0> r0|<r1> r1",
1061 node [ fillcolor = white, fontcolor = black ];
1065 h1 [ label = "h1\n0|<l> l|<r> r" ];
1066 h2 [ label = "h2\n1|<l> l|<r> r" ];
1067 h3 [ label = "h3\n1|<l> l|<r> r" ];
1068 h4 [ label = "h4\n1|<l> l|<r> r" ];
1069 h5 [ label = "h5\n1|<l> l|<r> r" ];
1070 h6 [ label = "h6\n1|<l> l|<r> r" ];
1072 root:r0 -> h1 [ style = invis ];
1074 h1:l -> h2 [ style = invis ];
1075 h1:r -> h3 [ style = invis ];
1084 Se comienza por eliminar la referencia de ``r0`` a ``h1``, que determina
1085 que ``h1`` se convirtió en *basura* (ver figura :vref:`fig:gc-rc-rm-1`). Esto
1086 conduce al decremento del contador de ``h2`` y ``h3`` que permanecen en el
1087 *live set* ya que sus contadores siguen siendo mayores a 0 (ver figura
1088 :vref:`fig:gc-rc-rm-2`).
1091 .. fig:: fig:gc-rc-up-1
1093 Cambio en la referencia ``h2.l`` :math:`\to` ``h2`` a ``h2.l``
1094 :math:`\to` ``h5`` (parte 1).
1098 Comienza ``update(h3.l, h5)``, se incrementa el contador de ``h5``.
1105 edge [ color = gray40 ];
1115 subgraph cluster_all {
1118 label = "root\nset|<r0> r0|<r1> r1",
1125 node [ fillcolor = white, fontcolor = black ];
1129 h1 [ label = "h1\n0|<l> l|<r> r" ];
1130 h2 [ label = "h2\n1|<l> l|<r> r" ];
1131 h3 [ label = "h3\n1|<l> l\n*|<r> r" ];
1132 h4 [ label = "h4\n1|<l> l|<r> r" ];
1133 h5 [ label = "h5\n2|<l> l|<r> r" ];
1134 h6 [ label = "h6\n1|<l> l|<r> r" ];
1136 root:r0 -> h1 [ style = invis ];
1137 h1:l -> h2 [ style = invis ];
1138 h1:r -> h3 [ style = invis ];
1143 h3:l -> h5 [ style = dotted, color = black ];
1150 Luego se procede a visitar las hijas de ``h3``, comenzando por ``h2``.
1157 edge [ color = gray40 ];
1167 subgraph cluster_all {
1170 label = "root\nset|<r0> r0|<r1> r1",
1177 node [ fillcolor = white, fontcolor = black ];
1181 h1 [ label = "h1\n0|<l> l|<r> r" ];
1182 h2 [ label = "h2\n1|<l> l|<r> r" ];
1183 h3 [ label = "h3\n1|<l> l\n*|<r> r" ];
1184 h4 [ label = "h4\n1|<l> l|<r> r" ];
1185 h5 [ label = "h5\n2|<l> l|<r> r" ];
1186 h6 [ label = "h6\n1|<l> l|<r> r" ];
1188 root:r0 -> h1 [ style = invis ];
1189 h1:l -> h2 [ style = invis ];
1190 h1:r -> h3 [ style = invis ];
1194 h3:l -> h2 [ style = bold, color = black ];
1195 h3:l -> h5 [ style = dotted, color = black ];
1202 Se decrementa el contador de ``h2`` y queda en 0 (pasa a ser
1203 *basura*). Se eliminan las referencias a las hijas.
1210 edge [ color = gray40 ];
1220 subgraph cluster_all {
1223 label = "root\nset|<r0> r0|<r1> r1",
1230 node [ fillcolor = white, fontcolor = black ];
1234 h1 [ label = "h1\n0|<l> l|<r> r" ];
1235 h2 [ label = "h2\n1|<l> l|<r> r" ];
1236 h3 [ label = "h3\n1|<l> l\n*|<r> r" ];
1237 h4 [ label = "h4\n1|<l> l|<r> r" ];
1238 h5 [ label = "h5\n2|<l> l|<r> r" ];
1239 h6 [ label = "h6\n1|<l> l|<r> r" ];
1241 root:r0 -> h1 [ style = invis ];
1242 h1:l -> h2 [ style = invis ];
1243 h1:r -> h3 [ style = invis ];
1245 h2:l -> h4 [ style = bold, color = black ];
1247 h3:l -> h2 [ style = invis ];
1248 h3:l -> h5 [ style = dotted, color = black ];
1254 .. fig:: fig:gc-rc-up-2
1256 Cambio en la referencia ``h2.l`` :math:`\to` ``h2`` a ``h2.l``
1257 :math:`\to` ``h5`` (parte 2).
1261 Se decrementa el contador de ``h4`` quedando en 0, pasa a ser
1262 *basura*. Se continúa con ``h5``.
1269 edge [ color = gray40 ];
1279 subgraph cluster_all {
1282 label = "root\nset|<r0> r0|<r1> r1",
1289 node [ fillcolor = white, fontcolor = black ];
1293 h1 [ label = "h1\n0|<l> l|<r> r" ];
1294 h2 [ label = "h2\n1|<l> l|<r> r" ];
1295 h3 [ label = "h3\n1|<l> l\n*|<r> r" ];
1296 h4 [ label = "h4\n0|<l> l|<r> r" ];
1297 h5 [ label = "h5\n2|<l> l|<r> r" ];
1298 h6 [ label = "h6\n1|<l> l|<r> r" ];
1300 root:r0 -> h1 [ style = invis ];
1301 h1:l -> h2 [ style = invis ];
1302 h1:r -> h3 [ style = invis ];
1304 h2:l -> h4 [ style = invis ];
1305 h2:r -> h5 [ style = bold, color = black ];
1306 h3:l -> h2 [ style = invis ];
1307 h3:l -> h5 [ style = dotted, color = black ];
1314 Se decrementa el contador de ``h5`` pero sigue siendo mayor que 0.
1321 edge [ color = gray40 ];
1331 subgraph cluster_all {
1334 label = "root\nset|<r0> r0|<r1> r1",
1341 node [ fillcolor = white, fontcolor = black ];
1345 h1 [ label = "h1\n0|<l> l|<r> r" ];
1346 h2 [ label = "h2\n1|<l> l|<r> r" ];
1347 h3 [ label = "h3\n1|<l> l\n*|<r> r" ];
1348 h4 [ label = "h4\n0|<l> l|<r> r" ];
1349 h5 [ label = "h5\n1|<l> l|<r> r" ];
1350 h6 [ label = "h6\n1|<l> l|<r> r" ];
1352 root:r0 -> h1 [ style = invis ];
1353 h1:l -> h2 [ style = invis ];
1354 h1:r -> h3 [ style = invis ];
1356 h2:l -> h4 [ style = invis ];
1357 h2:r -> h5 [ style = invis ];
1358 h3:l -> h5 [ style = bold, color = black ];
1359 h3:l -> h2 [ style = invis ];
1366 Se termina por actualizar la referencia de ``h3.l`` para que apunte
1374 edge [ color = gray40 ];
1384 subgraph cluster_all {
1387 label = "root\nset|<r0> r0|<r1> r1",
1394 node [ fillcolor = white, fontcolor = black ];
1398 h1 [ label = "h1\n0|<l> l|<r> r" ];
1399 h1 [ label = "h1\n0|<l> l|<r> r" ];
1400 h2 [ label = "h2\n0|<l> l|<r> r" ];
1401 h3 [ label = "h3\n1|<l> l|<r> r" ];
1402 h4 [ label = "h4\n0|<l> l|<r> r" ];
1403 h5 [ label = "h5\n1|<l> l|<r> r" ];
1404 h6 [ label = "h6\n1|<l> l|<r> r" ];
1406 root:r0 -> h1 [ style = invis ];
1407 h1:l -> h2 [ style = invis ];
1408 h1:r -> h3 [ style = invis ];
1410 h2:l -> h4 [ style = invis ];
1411 h2:r -> h5 [ style = invis ];
1413 h3:l -> h2 [ style = invis ];
1419 Luego se cambia una referencia (en vez de eliminarse) realizándose la
1420 operación ``update(h3.l, h5)``. Para esto primero se incrementa el contador
1421 de referencias de ``h5`` para evitar confundirlo accidentalmente con
1422 *basura* si se elimina alguna celda que apuntaba a ésta. Luego se procede
1423 a decrementar el contador de ``h2`` que queda en 0, transformándose en
1424 *basura* (ver figura :vref:`fig:gc-rc-up-1`). Lo mismo pasa cuando se
1425 desciende a ``h4``, pero al descender a ``h5`` y decrementar el contador,
1426 éste sigue siendo mayor que 0 (pues ``h3`` va a apuntar a ``h5``) así que
1427 permanece en el *live set*. Finalmente se termina de actualizar la
1428 referencia ``h3.l`` para que apunte a ``h5`` (ver figura
1429 :vref:`fig:gc-rc-up-2`).
1433 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1440 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1449 ----------------------------------------------------------------------------
1451 .. explicar la cantidad de cosas que hay (que son muchas) y dar algunos
1457 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1459 La cantidad de clasificaciones que pueden hacerse respecto a algoritmos de
1460 recolección de basura son muy grandes. Muchas de estas clasificaciones se
1461 superponen entre sí, habiendo algoritmos que aplican muchas técnicas a la
1462 vez o recolectores híbridos que aplican una técnica para algún tipo de
1463 objeto y otra para otros.
1465 A continuación se enumeran las clasificaciones más importantes que se
1466 pudieron observar en la investigación sobre el `estado del arte`_.
1473 Generalmente se llama recolección **directa** a aquella en la cual el
1474 compilador o lenguaje instrumenta al *mutator* de forma tal que la
1475 información de conectividad se mantenga activamente cada vez que hay un
1476 cambio en él. Normalmente se utiliza un contador de referencia en cada
1477 celda para este propósito, permitiendo almacenar en todo momento la
1478 cantidad de nodos que apuntan a ésta (dejando de lado el :ref:`problema de
1479 los ciclos <ref_gc_rc_cycles>`). Esto permite reclamar una celda
1480 instantáneamente cuando el *mutator* deja de hacer referencia a ella. Este
1481 tipo de recolectores son, inherentemente :ref:`incrementales <ref_gc_inc>`.
1483 Por el contrario, los recolectores **indirectos** normalmente no
1484 interfieren con el *mutator* en cada actualización del grafo de
1485 conectividad (exceptuando algunos :ref:`recolectores incrementales
1486 <ref_gc_inc>` que a veces necesitan instrumentar el *mutator* pero no para
1487 mantener el estado del grafo de conectividad completo). La recolección se
1488 dispara usualmente cuando el *mutator* requiere alocar memoria pero no hay
1489 más memoria libre conocida disponible y el recolector se encarga de generar
1490 la información de conectividad desde cero para determinar qué celdas son
1493 Estas son las dos grandes familias de recolectores, también conocidos como
1494 `conteo de referencias`_ (directa) y *traicing garbage collection*
1503 Recolección incremental es aquella que se realiza de forma intercalada con
1504 el *mutator*. En general el propósito es disminuir el tiempo de las pausas
1505 causadas por el recolector (aunque generalmente el resultado es un mayor
1506 costo en tiempo total de recolección).
1508 De los `algoritmos clásicos`_ el único que es incremental en su forma más
1509 básica es el `conteo de referencias`_. Otros recolectores pueden hacerse
1510 incrementales de diversas maneras, pero en general consta de hacer parte
1511 del trabajo de escanear el grafo de conectividad cada vez que el *mutator*
1512 aloca memoria. En general para hacer esto es también necesario instrumentar
1513 al *mutator* de forma tal que informe al recolector cada vez que cambia el
1514 grafo de conectividad, para que éste pueda marcar al sub-grafo afectado por
1515 el cambio como *desactualizado* y así re-escanearlo nuevamente en la
1518 En general la eficiencia de los recolectores incrementales disminuye
1519 considerablemente cuando el *mutator* actualiza muy seguido el grafo de
1520 conectividad, porque debe re-escanear sub-grafos que ya había escaneado una
1521 y otra vez. A esto se debe también que en general el tiempo de
1522 procesamiento total de un recolector incremental sea mayor que uno no
1523 incremental, aunque el tiempo de pausa de una recolección sea menor.
1526 .. _ref_gc_concurrent:
1528 Concurrente / *stop-the-world*
1529 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1531 Los recolectores concurrentes son aquellos que pueden correr en paralelo
1532 con el *mutator*. Por el contrario, aquellos que pausan el *mutator* para
1533 realizar la recolección son usualmente denominados *stop-the-world* (*parar
1534 el mundo*), haciendo referencia a que pausan todos los hilos del *mutator*
1535 para poder escanear el grafo de conectividad de forma consistente.
1537 Para lograr que un recolector sea concurrente generalmente el mecanismo es
1538 similar al necesario para hacer un :ref:`recolector incremental
1539 <ref_gc_inc>`: hay que instrumentar al *mutator* para que informe al
1540 recolector cuando se realiza algún cambio en el grafo de conectividad, de
1541 forma tal que pueda volver a escanear el sub-grafo afectado por el cambio.
1543 Esto también trae como consecuencia el incremento en el tiempo total que
1544 consume el recolector, debido a la necesidad de re-escanear sub-grafos que
1545 han sido modificados.
1551 1. Instrumentar el *mutator* de forma tal de que informe al recolector cada
1552 vez que cambia el grafo de conectividad, de manera que pueda re-escanear
1553 el sub-grafo que afectado por el cambio.
1555 2. Tomar una foto de la memoria y escanear esa foto estática en vez de la
1556 memoria original, de forma que cualquier cambio en del *mutator* no
1557 afecte la vista de la memoria del recolector.
1559 Ambos métodos tienen ventajas y desventajas. El primero no necesita hacer
1560 una copia costosa de la memoria, pero existe la posibilidad de que el
1561 recolector nunca pueda escanear el grafo de conectividad completo, si el
1562 *mutator* modifica el grafo más rápido de lo que el recolector lo escanea.
1563 Si bien hay formas de evitar esto, es usual que el trabajo de recolección
1564 se vea considerablemente incrementado por la cantidad de veces que hay que
1565 re-escanear el grafo. El segundo método puede ser costoso por las copias
1566 necesarias (aunque esto puede ser mitigado empleando alguna forma de *COW*
1567 [#gccow]_) y existe la posibilidad de que no se recuperen celdas que el
1571 .. [#gccow] *COW* (del inglés *Copy On Write* o *copiar al escribir*) es
1572 una técnica muy utilizada para disminuir las copias innecesarias,
1573 evitando hacer la copia hasta que haya una modificación. Mientras no
1574 hayan modificaciones no es necesario realizar una copia porque todos los
1575 interesados verán la misma información. Esta técnica es utilizada por el
1576 *kernel* de Linux_ por ejemplo, para que varias instancias de un mismo
1577 proceso puedan compartir la memoria.
1580 Boehm, H., and M. Weiser, "Garbage Collection in an Uncooperative
1581 Environment", Software Practice & Experience, September 1988, pp. 807-820.
1582 http://www.hpl.hp.com/personal/Hans_Boehm/spe_gc_paper/preprint.pdf
1583 (http://www.hpl.hp.com/personal/Hans_Boehm/spe_gc_paper/)
1585 Boehm, H., "Space Efficient Conservative Garbage Collection", Proceedings
1586 of the ACM SIGPLAN '93 Conference on Programming Language Design and
1587 Implementation, SIGPLAN Notices 28, 6 (June 1993), pp. 197-206.
1588 http://www.hpl.hp.com/personal/Hans_Boehm/gc/papers/pldi93.ps.Z
1590 Boehm, H., A. Demers, and S. Shenker, "Mostly Parallel Garbage Collection",
1591 Proceedings of the ACM SIGPLAN '91 Conference on Programming Language
1592 Design and Implementation, SIGPLAN Notices 26, 6 (June 1991), pp. 157-164.
1593 http://www.hpl.hp.com/personal/Hans_Boehm/gc/papers/pldi91.ps.Z
1595 Implementación de Bohem GC
1596 http://www.hpl.hp.com/personal/Hans_Boehm/gc/gcdescr.html
1598 Interfaz de Bohem GC
1599 http://www.hpl.hp.com/personal/Hans_Boehm/gc/gcinterface.html
1602 .. include:: links.rst
1604 .. vim: set ts=3 sts=3 sw=3 et tw=78 :