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.
11 ============================================================================
15 ----------------------------------------------------------------------------
17 *Recolección de basura* se refiere a la recuperación automática de memoria
18 del *heap* [#gcheap]_ una vez que el programa ha dejado de hacer referencia
19 a ella (y por lo tanto, ha dejado de utilizarla).
21 A medida que el tiempo pasa, cada vez los programas son más complejos y es
22 más compleja la administración de memoria. Uno de los aspectos más
23 importantes de un recolector de basura es lograr un mayor nivel de
24 abstracción y modularidad, dos conceptos claves en la ingeniería de
25 software [JOLI96]_. En particular, al diseñar o programar bibliotecas, de
26 no haber un recolector de basura, **la administración de memoria pasa a ser
27 parte de la interfaz**, lo que produce que los módulos tengan un mayor
28 grado de acoplamiento.
30 Además hay una incontable cantidad de problemas asociados al manejo
31 explícito de memoria que simplemente dejan de existir al utilizar un
32 recolector de basura. Por ejemplo, los errores en el manejo de memoria
33 (como *buffer overflows* [#gcbuff]_ o *dangling pointers* [#gcdang]_) son
34 la causa más frecuente de problemas de seguridad [BEZO06]_.
36 La recolección de basura nació junto a Lisp_ a finales de 1950 y en los
37 siguientes años estuvo asociada principalmente a lenguajes funcionales,
38 pero en la actualidad está presente en prácticamente todos los lenguajes de
39 programación, de alto o bajo nivel, aunque sea de forma opcional. En los
40 últimos 10 años tuvo un gran avance, por la adopción en lenguajes de
41 desarrollo rápido utilizados mucho en el sector empresarial, en especial
42 Java_, que fue una plataforma de facto para la investigación y desarrollo
43 de recolectores de basura (aunque no se limitaron a este lenguaje las
46 En las primeras implementaciones de recolectores de basura la penalización
47 en la eficiencia del programa se volvía prohibitiva para muchas
48 aplicaciones. Es por esto que hubo bastante resistencia a la utilización de
49 recolectores de basura, pero el avance en la investigación fue haciendo que
50 cada vez sea una alternativa más viable al manejo manual de memoria,
51 incluso para apliaciones con altos requerimientos de eficiencia. En la
52 actualidad un programa que utiliza un recolector moderno puede ser
53 comparable en eficiencia con uno que utiliza un esquema manual. En
54 particular, si el programa fue diseñado con el recolector de basura en
55 mente en ciertas circunstancias puede ser incluso más eficiente que uno que
56 hace manejo explícito de la memoria. Muchos recolectores mejoran la
57 localidad de referencia, haciendo que el programa tenga un mejor
58 comportamiento con el caché y la memoria virtual.
60 El recolector de basura debe tener un comportamiento correcto y predecible
61 para que sea útil, si el programador no puede confiar en el recolector
62 de basura, éste se vuelve más un problema que una solución, porque
63 introduce nuevos puntos de falla en los programas, y lo que es peor,
64 puntos de falla no controlados por el programador, volviendo mucho más
65 difícil la búsqueda de errores.
68 .. [#gcheap] *Heap* es un área de memoria que se caracteriza por ser
69 dinámica (a diferencia del área de memoria estática que está disponible
70 durante toda la ejecución de un programa). Un programa puede reservar
71 memoria en tiempo de ejecución según sea necesario y liberarla cuando ya
72 no la necesita. A diferencia del *stack*, la duración de la *reserva* no
73 está atada a un bloque de código.
74 .. [#gcbuff] Un *buffer overflow* (*desbordamiento de memoria* en
75 castellano) se produce cuando se copia un dato a un área de memoria que
76 no es lo suficientemente grande para contenerlo. Esto puede producir que
77 el programa sea abortado por una violación de segmento, o peor,
78 sobreescribir un área de memoria válida, en cuyo caso los resultados son
80 .. [#gcdang] Un *dangling pointer* (*puntero colgante* en castellano) es un
81 puntero que apunta a un área de memoria inválida. Ya sea porque el
82 elemento apuntado no es el mismo tipo o porque la memoria ya ha sido
83 liberada. Al ser desreferenciado, los resultados son impredecibles, el
84 programa podría abortarse por una violación de segmento o podrían pasar
85 peores cosas si el área de memoria fue realocada para almacenar otro
91 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
93 Los programas pueden hacer uso principalmente de 4 áreas de memoria:
96 Se trata de la memoria más básica de una computadora. Es el área de
97 memoria en la que puede operar realmente el procesador, es extremadamente
98 escasa y generalmente su uso es administrado por el lenguaje de
99 programación (o compilador más específicamente). Excepto en situaciones
100 muy particulares, realizando tareas de muy bajo nivel, un programador
101 nunca manipula los registros explícitamente.
103 Área de memoria estática:
104 Es la forma de memoria más simple que un programador utiliza
105 explícitamente. En general las variables globales se almacenan en este
106 área, que es parte inherente del programa y está disponible durante toda
107 su ejecución, por lo tanto nunca cambia su capacidad en tiempo de
108 ejecución. Es la forma más básica de administrar memoria, pero tiene una
109 limitación fundamental: **el tamaño de la memoria tiene que ser conocido
110 en tiempo de compilación**. Los primeros lenguajes de programación solo
111 contaban con este tipo de memoria (además de los registros del
115 Los primeros lenguajes de programación que hicieron uso de una pila
116 aparecieron en el año 1958 (Algol-58 y Atlas Autocode) y fueron los
117 primeros en introducir estructura de bloques, almacenando las
118 variables locales a estos bloques utilizando una pila [JOLI96]_.
119 Esto permite utilizar recursividad y tener un esquema simple de memoria
120 dinámica. Sin embargo este esquema es muy limitado porque el orden de
121 reserva y liberación de memoria tiene que estar bien establecido. Una
122 celda [#gccelda]_ alocada antes que otra nunca puede ser liberada antes
126 A diferencia del *stack*, el *heap* provee un área de memoria que puede
127 ser obtenida dinámicamente pero sin limitaciones de orden. Es el tipo de
128 memoria más flexible y por lo tanto el más complejo de administrar; razón
129 por la cual existen los recolectores de basura.
131 La recolección de basura impone algunas restricciones sobre la manera de
132 utilizar el *heap*. Debido a que un recolector de basura debe ser capaz de
133 determinar el grafo de conectividad de la memoria en uso, es necesario que
134 el programa siempre tenga alguna referencia a las celdas activas en los
135 registros, memoria estática o *stack* (normalmente denominado *root set*).
137 Esto implica que una celda sea considerada basura si y sólo si no puede ser
138 alcanzada a través del grafo de conectividad que se comienza a recorrer
139 desde el *root set*. Por lo tanto, una celda está *viva* si y sólo si su
140 dirección de memoria está almacenada en una celda *raíz* (parte del *root
141 set*) o si está almacenada en otra celda *viva* del *heap*.
143 Expresado más formalmente, dada la relación :math:`M \to N`, donde
144 :math:`M` es una celda del *heap* o parte del *root set* y :math:`N` es una
145 celda del *heap*, definida como:
149 M \to N \Longleftrightarrow M \text{ almacena un puntero a } N
151 El conjunto de celdas vivas (o *live set*) queda determinado por:
155 vivas = \left\lbrace N \in Celdas \big/
156 ( \exists r \in Raices / r \to N ) \vee (\exists M \in vivas / M \to N )
159 Cabe aclarar que esta es una definición conceptual, asumiendo que el
160 programa siempre limpia una dirección de memoria almacenada en el *root
161 set* o una celda del *heap* cuando la celda a la que apunta no va a ser
162 utilizada nuevamente. Esto no es siempre cierto y los falsos positivos que
163 esto produce se conoce como un tipo de pérdida de memoria (que es posible
164 incluso al utilizar un recolector de basura) llamada pérdida de memoria
165 *lógica*. Esto puede no ser evitable (incluso cuando el programador no
166 cometa errores) en lenguajes de programación que requieran un recolector de
169 Por último, siendo que el recolector de basura es parte del programa de
170 forma indirecta, es común ver en la literatura que se direfencia entre
171 2 partes del programa, el recolector de basura y el programa en sí. Dado
172 que para el recolector de basura, lo único que interesa conocer del
173 programa en sí son los cambios al grafo de conectividad de las celdas,
174 normalmente se lo llama *mutator* (mutador).
177 .. [#gccelda] En general en la literatura se nombra a una porción de
178 memoria alocada individualmente *celda*, *nodo* u *objeto*
179 indistintamente. En este trabajo se utilizará la misma nomenclatura
180 (haciendo mención explícita cuando alguno de estos términos se refiera
181 a otra cosa, como al nodo de una lista o a un objeto en el sentido de
182 programación orientada a objetos).
186 Recorrido del grafo de conectividad
187 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
189 El problema de encontrar las celdas *vivas* de un programa se reduce a recorrer
190 un grafo dirigido. El grafo se define como:
196 Donde :math:`V` es el conjunto de vértices, dado por las celdas de memoria
197 y :math:`A` es un conjunto de pares ordenados (aristas), dado por la
198 relación :math:`M \rightarrow N` (es decir, los punteros).
200 El grafo comienza a recorrerse desde el *root set* y todos los vértices que
201 fueron visitados componen el *live set*; el resto de los vértices son
204 Más formalmente, Definimos:
207 secuencia de vértices tal que cada uno de los vértices tiene una arista
208 al próximo vértice en la secuencia. Todo camino finito tiene un *vértice
209 inicial* y un *vértice final* (llamados en conjunto *vértices
210 terminales*). Cualquier vértice no terminal es denominado *vértice
215 \underset{v_1 \rightarrow v_N}{C} = \left\lbrace
216 v_1, \dotsc, v_N \in V \big/ \underset{i \in [1,N-1]}{\forall v_i}
217 \exists (v_i \to v_{i+1}) \in A
221 decimos que :math:`M` está *conectado* a :math:`N` si y sólo si existe un
222 camino de :math:`M` a :math:`N`.
226 M \mapsto N \Longleftrightarrow \exists \underset{M \to N}{C} \in G
229 el conjunto de celdas *vivas* está dado por todos los vértices
230 (:math:`v`) del grafo para los cuales existe una raíz en el *root set*
231 que esté conectada a él.
235 Live \thickspace set = \left\lbrace v \in V \big/
236 \left( \exists r \in Root \thickspace set \big/ r \mapsto v \right)
240 la basura, o celdas *muertas*, quedan determinadas entonces por todas las
241 celdas del *heap* que no son parte del *live set*.
245 Basura = V - Live \thickspace set
247 Esto es, efectivamente, una partición del *heap*.
253 \begin{figure}[htp] \centering
260 node [ shape = record, width = 0, height = 0 ];
262 subgraph cluster_heap {
268 subgraph cluster_live {
281 subgraph cluster_garbage {
286 node [ style = filled, fillcolor = white ];
291 subgraph cluster_root {
296 node [ style = filled, fillcolor = gray96 ];
300 r0 -> h1 -> h2 -> h5;
301 r1 -> h5 -> h6 -> h1;
309 \caption{Distintas partes de la memoria, incluyendo relación entre
310 basura, live set, heap y root set.}
313 Al proceso de visitar los vértices *conectados* desde el *root set* se lo
314 denomina *marcado*, *fase de marcado* o *mark phase* en inglés, debido
315 a que es necesario marcar los vértices para evitar visitar 2 veces el mismo
316 nodo en casos de que el grafo contenga ciclos [#gccycle]_. De forma similar
317 a la búsqueda, que puede realizarse *primero a lo ancho* (*breadth-first*)
318 o *primero a lo alto* (*depth-first*) del grafo, el marcado de un grafo
319 también puede realizarse de ambas maneras. Cada una podrá o no tener
320 efectos en la eficiencia, en particular dependiendo de la aplicación puede
321 convenir uno u otro método para lograr una mejor localidad de referencia
322 y de esta manera tener un mejor comportamiento de la memoria virtual o del
325 Un algoritmo simple (recursivo) de marcado *primero a lo alto* puede ser
326 el siguiente (asumiendo que partimos con todos los vértices sin marcar)
332 for (src, dst) in v.edges
339 Una vez concluido el marcado, sabemos que todos los vértices con la marca
340 son parte del *live set* y que todos los vértices no marcados son *basura*.
341 Esto es conocido también como **abstracción bicolor**, dado que en la
342 literatura se habla muchas veces de *colorear* las celdas. En general, una
343 celda sin marcar es de color blanco y una marcada de color negro.
351 \subfigure[TODO texto más largo y más largo y larguísimo porque tiene
352 muchas cosas que decir]{
359 node [ shape = record, width = 0, height = 0];
360 edge [ color = gray40 ];
362 subgraph cluster_all {
365 label = "root\nset|<r0> r0\n*|<r1> r1",
371 node [ style = filled, fillcolor = gray25, fontcolor = white ];
375 root:r0 -> h1 [ style = bold, color = black ];
376 h1 -> h2 -> h5 -> h1;
393 node [ shape = record, width = 0, height = 0 ];
394 edge [ color = gray40 ];
396 subgraph cluster_all {
399 label = "root\nset|<r0> r0\n*|<r1> r1",
405 node [ style = filled, fillcolor = gray25, fontcolor = white ];
409 root:r0 -> h1 [ color = gray10 ];
410 h1 -> h2 [ style = bold, color = black ];
428 node [ shape = record, width = 0, height = 0 ];
429 edge [ color = gray40 ];
431 subgraph cluster_all {
434 label = "root\nset|<r0> r0\n*|<r1> r1",
440 node [ style = filled, fillcolor = gray25, fontcolor = white ];
444 root:r0 -> h1 [ color = gray10 ];
445 h1 -> h2 [ color = gray10 ];
446 h2 -> h5 [ style = bold, color = black ];
457 \caption{TODO, part 1}
469 node [ shape = record, width = 0, height = 0 ];
470 edge [ color = gray40 ];
472 subgraph cluster_all {
475 label = "root\nset|<r0> r0\n*|<r1> r1",
481 node [ style = filled, fillcolor = gray25, fontcolor = white ];
485 root:r0 -> h1 [ color = gray10 ];
486 h1 -> h2 [ color = gray10 ];
487 h2 -> h5 [ color = gray10 ];
488 h5 -> h1 [ style = bold, color = black ];
505 node [ shape = record, width = 0, height = 0 ];
506 edge [ color = gray40 ];
508 subgraph cluster_all {
511 label = "root\nset|<r0> r0|<r1> r1\n*",
517 node [ style = filled, fillcolor = gray25, fontcolor = white ];
521 root:r0 -> h1 [ color = gray10 ];
522 h1 -> h2 [ color = gray10 ];
523 h2 -> h5 [ color = gray10 ];
524 h5 -> h1 [ color = gray10 ];
525 root:r1 -> h6 [ style = bold, color = black ];
542 node [ shape = record, width = 0, height = 0 ];
543 edge [ color = gray40 ];
545 subgraph cluster_all {
548 label = "root\nset|<r0> r0|<r1> r1\n*",
554 node [ style = filled, fillcolor = gray25, fontcolor = white ];
558 root:r0 -> h1 [ color = gray10 ];
559 h1 -> h2 [ color = gray10 ];
560 h2 -> h5 [ color = gray10 ];
561 h5 -> h1 [ color = gray10 ];
562 root:r1 -> h6 [ color = gray10 ];
563 h6 -> h2 [ style = bold, color = black ];
572 \caption{TODO, parte 2}
576 .. [#gccycle] Un ciclo es un camino donde el *vértice inicial* es el mismo
577 que el *vértice final*. Por lo tanto, los *vértices terminales* son
578 completamente arbitrarios, ya que cualquier *vértice interior* puede ser
579 un *vértice terminal*.
580 .. [#gcpseudo] Para presentar los algoritmos se utiliza una forma simple de
581 pseudo-código. El pseudo-código se escribe en inglés para que pueda ser
582 más fácilmente contrastado con la literatura, que está en inglés. Para
583 diferenciar posiciones de memoria y punteros de las celdas en sí, se usa
584 la misma sintaxis que C, ``r*`` denota una referencia o puntero y ``*r``
585 denota "objeto al que apunta ``r``\ ". Se sobreentiende que ``r = o``
586 siempre toma la dirección de memoria de ``o``.
593 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
595 Muchos algoritmos utilizan tres colores para realizar el marcado. El tercer
596 color, gris generalmente, indica que una celda debe ser visitada. Esto
597 permite algoritmos :ref:`concurrentes <ref_gc_concurrent>`
598 e :ref:`incrementales <ref_gc_inc>`, además de otro tipo de optimizaciones.
599 Entonces, lo que plantea esta abtracción es una nueva partición del heap al
600 momento de marcar, esta vez son 3 porciones: blanca, gris y negra.
602 Al principio todas las celdas se pintan de blanco, excepto el *root set*
603 que se punta de gris. Luego se van obteniendo celdas del conjunto de las
604 grises y se las pinta de negro, pintando sus hijas directas de gris.
606 Una vez que no hay más celdas grises, tenemos la garantía de que las celdas
607 negras serán el *live set* y las celdas blancas *basura*. Esto se debe
608 a que siempre se mantiene esta invariante: **ninguna celda negra apunta
609 directamente a una celda blanca**. Las celdas blancas siempre son apuntadas
610 por celdas blancas o grises. Entonces, siempre que el conjunto de celdas
611 grises sea vacío, no habrán celdas negras conectadas a blancas, siendo las
612 celdas blancas *basura*.
614 El algoritmo básico para marcar con tres colores es el siguiente (asumiendo
615 que todas las celdas parten pintadas de blanco, es decir, el conjunto
616 blanco contiene todas las celdas de memoria y los conjuntos negro y gris
622 while not gray_set.empty()
625 for (src, dst) in v.edges
630 Es simple notar que este algoritmo es naturalmente no recursivo, lo que de
631 por sí ya presenta una ventaja sobre el marcado *bicolor*.
638 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
640 En general todos los algoritmos de recolección de basura utilizan servicios
641 de una capa inferior [#gclowlayer]_ y proveen servicios a una capa superior
644 A continuación se presentan las primitivas en común que utilizan todos los
645 recolectores a lo largo de este documento.
647 Servicios utilizados por el recolector son los siguientes:
649 :math:`alloc() \to cell`:
650 obtiene una nueva celda de memoria. El mecanismo por el cual se obtiene
651 la celda es indistinto para esta sección, puede ser de una lista libre,
652 puede ser de un administrador de memoria de más bajo nivel provisto por
653 el sistema operativo o la biblioteca estándar de C (``malloc()``), etc.
654 Cómo organizar la memoria es un área de investigación completa y si bien
655 está estrechamente relacionada con la recolección de basura, en este
656 trabajo no se prestará particular atención a este aspecto (salvo casos
657 donde el recolector impone una cierta organización de memoria en el *low
658 level allocator*). Por simplicidad también asumiremos (a menos que se
659 indique lo contrario) que las celdas son de tamaño fijo. Esta restricción
660 normalmente puede ser fácilmente relajada (en los recolectores que la
664 libera una celda que ya no va a ser utilizada. La celda liberada debe
665 haber sido obtenida mediante ``alloc()``.
667 Y los servicios básicos proporcionados por el recolector son los
670 :math:`new() \to cell`:
671 obtiene una celda de memoria para ser utilizada por el programa.
673 :math:`update(ref, cell)`:
674 notifica al recolector que la referencia :math:`ref` ahora apunta
675 a :math:`cell`. Visto más formalmente, sería análogo a decir que hubo un
676 cambio en la conectividad del grafo: la arista :math:`src \to old` cambia
677 por :math:`src \to new` (donde :math:`src` es la celda que contiene la
678 referencia :math:`ref`, :math:`old` es la celda a la que apunta la
679 referencia :math:`ref` y :math:`new` es el argumento :math:`cell`). Si
680 :math:`cell` es ``null``, sería análogo a informar que se elimina la
681 arista :math:`src \to old`.
684 este servicio, según el algoritmo, puede ser utilizado para informar un
685 cambio en la conectividad del grafo, la eliminación de una arista
686 (análogo a :math:`update(ref, null)` pero sin proporcionar información
687 sobre la arista a eliminar). Esto es generalmente útil solo en
688 :ref:`conteo de referencias <ref_gc_rc>`. Para otros recolectores puede
689 significar que el usuario asegura que no hay más referencias a esta
690 celda, es decir, análogo a eliminar el conjunto de aristas
691 :math:`\big\lbrace (v, w) \in A , v \in Live \thickspace set , w \in
692 Live \thickspace set \big/ w = cell`.
695 indica al recolector que debe hacer un análisis del grafo de conectividad
696 en busca de *basura*. Generalmente este servicio es invocado por el
697 propio recolector cuando no hay más celdas reciclables.
699 No todos los servicios son implementados por todos los recolectores, pero
700 son lo suficientemente comunes como para describirlos de forma general en
701 esta sección. Algunos son principalmente ideados para uso interno del
702 recolector, aunque en ciertas circunstancias pueden ser utilizados por el
706 .. [#gclowlayer] En general estos servicios están provistos directamente
707 por el sistema operativo pero también pueden estar dados por un
708 administrador de memoria de bajo nivel (o *low level allocator* en
710 .. [#gchilayer] En general estos servicios son utilizados directamente por
711 el lenguaje de programación, pero pueden ser utilizados directamente por
712 el usuario del lenguaje si éste interatúa con el recolector, ya sea por
713 algún requerimiento particular o porque el lenguaje no tiene soporte
714 diercto de recolección de basura y el recolector está implementado como
715 una biblioteca de usuario.
722 ----------------------------------------------------------------------------
724 En la literatura se encuentran normalmente referencias a 3 algoritmos
725 clásicos, que son utilizados generalmente como bloques básicos para
726 construir recolectores más complejos. Se presentan las versiones históricas
727 más simples a fin de facilitar la comprensión conceptual.
733 Conteo de referencias
734 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
736 Se trata del algoritmo más antiguo de todos, implementado por primera vez
737 por `John McCarthy`_ para Lisp_ a finales de 1950. Se trata de un método
738 :ref:`directo <ref_gc_direct>` e :ref:`incremental <ref_gc_inc>` por
739 naturaleza, ya que distribuye la carga de la recolección de basura durante
740 toda la ejecución del programa, cada vez que el *mutator* cambia la
741 conectividad de algún nodo del grafo de conectividad.
743 El método consiste en tener un contador asociado a cada celda que contenga
744 la cantidad de celdas **vivas** que apuntan a ésta. Es decir, es la
745 cardinalidad del conjunto de aristas que tienen por destino a la celda.
746 Formalmente podemos definir el contador math:`rc(v)` (de *reference
747 counter* en inglés) de la siguiente manera:
753 (v_1, v_2) \in A \big/
754 v_1 \in Live \thickspace set \cup Root \thickspace set
759 El *mutator* entonces debe actualizar este contador cada vez que el grafo
760 de conectividad cambia, es decir, cada vez que se agrega, modifica
761 o elimina una arista del grafo (o visto de una forma más cercana al código,
762 cada vez que se agrega, modifica o elimina un puntero).
764 Esta invariante es fundamental para el conteo de referencias, porque se
765 asume que si el contador es 0 entonces el *mutator* no tiene ninguna
766 referencia a la celda y por lo tanto es *basura*:
770 rc(v) = 0 \Rightarrow v \in Basura
772 Para mantener esta invariante el *mutator*, cada vez que cambia un puntero
773 debe decrementar en 1 el contador de la celda a la que apuntaba
774 antiguamente e incrementar en 1 el contador de la celda a la que apunta
775 luego de la modificación. Esto asegura que la invariante se mantenga
776 durante toda la ejecución del programa. Si al momento de decrementar un
777 contador éste queda en 0, la celda asociada debe liberarse de forma de
778 poder ser reciclada. Esto implica que si esta celda almacena punteros, los
779 contadores de las celdas apuntadas deben ser decrementados también, porque
780 solo deben almacenarse en el contador las aristas del *live set* para
781 mantener la invariante. De esto puede resultar que otros contadores de
782 referencia queden en 0 y más celdas sean liberadas. Por lo tanto,
783 teóricamente la complejidad de eliminar una referencia puede ser
784 :math:`O(\lvert Live \thickspace set \rvert)` en el peor caso.
786 Las primitivas implementadas para este tipo de recolector son las
787 siguientes (acompañadas de una implementación básica)::
797 cell.rc = cell.rc - 1
799 for child* in cell.children
804 cell.rc = cell.rc + 1
812 A continuación se presenta un ejemplo gráfico para facilitar la comprensión
815 Por simplicidad se asumen celdas de tamaño fijo con dos punteros, ``left``
816 y ``right`` (estructura clásica para representar un árbol binario) y se
817 muestra el contador de referencias abajo del nombre de cada celda. Se parte
818 con una pequeña estructura ya construída y se muestra primero lo que ocurre
819 al eliminar la referencia :math:`r0 \to h1`:
834 edge [ color = gray40 ];
844 subgraph cluster_all {
847 label = "root\nset|<r0> r0\n*|<r1> r1",
853 h1 [ label = "h1\n1|<l> left|<r> right" ];
854 h2 [ label = "h2\n2|<l> left|<r> right" ];
855 h3 [ label = "h3\n2|<l> left|<r> right" ];
856 h4 [ label = "h4\n1|<l> left|<r> right" ];
857 h5 [ label = "h5\n1|<l> left|<r> right" ];
858 h6 [ label = "h6\n1|<l> left|<r> right" ];
860 root:r0 -> h1 [ style = bold, color = black ];
881 edge [ color = gray40 ];
891 subgraph cluster_all {
894 label = "root\nset|<r0> r0\n*|<r1> r1",
901 node [ fillcolor = white, fontcolor = black ];
905 h1 [ label = "h1\n0|<l> left|<r> right" ];
906 h2 [ label = "h2\n2|<l> left|<r> right" ];
907 h3 [ label = "h3\n2|<l> left|<r> right" ];
908 h4 [ label = "h4\n1|<l> left|<r> right" ];
909 h5 [ label = "h5\n1|<l> left|<r> right" ];
910 h6 [ label = "h6\n1|<l> left|<r> right" ];
912 root:r0 -> h1 [ style = invis ];
914 h1:l -> h2 [ style = bold, color = black ];
926 \caption{TODO, part 1}
938 edge [ color = gray40 ];
948 subgraph cluster_all {
951 label = "root\nset|<r0> r0\n*|<r1> r1",
958 node [ fillcolor = white, fontcolor = black ];
962 h1 [ label = "h1\n0|<l> left|<r> right" ];
963 h2 [ label = "h2\n1|<l> left|<r> right" ];
964 h3 [ label = "h3\n2|<l> left|<r> right" ];
965 h4 [ label = "h4\n1|<l> left|<r> right" ];
966 h5 [ label = "h5\n1|<l> left|<r> right" ];
967 h6 [ label = "h6\n1|<l> left|<r> right" ];
969 root:r0 -> h1 [ style = invis ];
971 h1:l -> h2 [ style = invis ];
972 h1:r -> h3 [ style = bold, color = black ];
990 edge [ color = gray40 ];
1000 subgraph cluster_all {
1003 label = "root\nset|<r0> r0|<r1> r1",
1010 node [ fillcolor = white, fontcolor = black ];
1014 h1 [ label = "h1\n0|<l> left|<r> right" ];
1015 h2 [ label = "h2\n1|<l> left|<r> right" ];
1016 h3 [ label = "h3\n1|<l> left|<r> right" ];
1017 h4 [ label = "h4\n1|<l> left|<r> right" ];
1018 h5 [ label = "h5\n1|<l> left|<r> right" ];
1019 h6 [ label = "h6\n1|<l> left|<r> right" ];
1021 root:r0 -> h1 [ style = invis ];
1023 h1:l -> h2 [ style = invis ];
1024 h1:r -> h3 [ style = invis ];
1035 \caption{TODO, parte 2}
1038 Como se observa, la celda ``h1`` queda con el contador en 0, por lo tanto
1039 pasa a ser *basura*. Los contadores de las celdas a las que apuntaban
1040 ``h1.left`` y ``h1.right`` son decrementados, pero no se detecta más basura
1041 porque sigue existiendo un camino del *root set* a aquellas celdas, y por
1042 transitividad a todas aquellas celdas apuntadas por éstas. Es por esto que
1043 no es necesario seguir recorriendo el grafo de conectividad.
1045 A continuación se muestra como opera el algoritmo al cambiar a donde apunta
1046 ``h3.left``, haciéndola apuntar a ``h5``. Es decir, que pasa luego de la
1047 operación ``update(h3.left, h5)`` (por simplicidad no se muestra más la
1048 celda ``h1`` que ahora es *basura*):
1063 edge [ color = gray40 ];
1073 subgraph cluster_all {
1076 label = "root\nset|<r0> r0|<r1> r1",
1083 node [ fillcolor = white, fontcolor = black ];
1086 h2 [ label = "h2\n1|<l> left|<r> right" ];
1087 h3 [ label = "h3\n1|<l> left\n*|<r> right" ];
1088 h4 [ label = "h4\n1|<l> left|<r> right" ];
1089 h5 [ label = "h5\n1|<l> left|<r> right" ];
1090 h6 [ label = "h6\n1|<l> left|<r> right" ];
1095 h3:l -> h2 [ style = bold, color = black ];
1110 edge [ color = gray40 ];
1120 subgraph cluster_all {
1123 label = "root\nset|<r0> r0|<r1> r1",
1130 node [ fillcolor = white, fontcolor = black ];
1133 h2 [ label = "h2\n1|<l> left|<r> right" ];
1134 h3 [ label = "h3\n1|<l> left\n*|<r> right" ];
1135 h4 [ label = "h4\n1|<l> left|<r> right" ];
1136 h5 [ label = "h5\n2|<l> left|<r> right" ];
1137 h6 [ label = "h6\n1|<l> left|<r> right" ];
1142 h3:l -> h2 [ style = bold, color = black ];
1143 h3:l -> h5 [ style = dotted, color = black ];
1151 \caption{TODO, part 1}
1163 edge [ color = gray40 ];
1173 subgraph cluster_all {
1176 label = "root\nset|<r0> r0|<r1> r1",
1183 node [ fillcolor = white, fontcolor = black ];
1187 h2 [ label = "h2\n0|<l> left|<r> right" ];
1188 h3 [ label = "h3\n1|<l> left\n*|<r> right" ];
1189 h4 [ label = "h4\n1|<l> left|<r> right" ];
1190 h5 [ label = "h5\n2|<l> left|<r> right" ];
1191 h6 [ label = "h6\n1|<l> left|<r> right" ];
1194 h2:l -> h4 [ style = bold, color = black ];
1196 h3:l -> h2 [ style = invis ];
1197 h3:l -> h5 [ style = dotted, color = black ];
1212 edge [ color = gray40 ];
1222 subgraph cluster_all {
1225 label = "root\nset|<r0> r0|<r1> r1",
1232 node [ fillcolor = white, fontcolor = black ];
1236 h2 [ label = "h2\n0|<l> left|<r> right" ];
1237 h3 [ label = "h3\n1|<l> left\n*|<r> right" ];
1238 h4 [ label = "h4\n0|<l> left|<r> right" ];
1239 h5 [ label = "h5\n2|<l> left|<r> right" ];
1240 h6 [ label = "h6\n1|<l> left|<r> right" ];
1243 h2:l -> h4 [ style = invis ];
1244 h2:r -> h5 [ style = bold, color = black ];
1245 h3:l -> h2 [ style = invis ];
1246 h3:l -> h5 [ style = dotted, color = black ];
1254 \caption{TODO, part 2}
1266 edge [ color = gray40 ];
1276 subgraph cluster_all {
1279 label = "root\nset|<r0> r0|<r1> r1",
1286 node [ fillcolor = white, fontcolor = black ];
1290 h2 [ label = "h2\n0|<l> left|<r> right" ];
1291 h3 [ label = "h3\n1|<l> left\n*|<r> right" ];
1292 h4 [ label = "h4\n0|<l> left|<r> right" ];
1293 h5 [ label = "h5\n1|<l> left|<r> right" ];
1294 h6 [ label = "h6\n1|<l> left|<r> right" ];
1297 h2:l -> h4 [ style = invis ];
1298 h2:r -> h5 [ style = invis ];
1299 h3:l -> h5 [ style = bold, color = black ];
1300 h3:l -> h2 [ style = invis ];
1315 edge [ color = gray40 ];
1325 subgraph cluster_all {
1328 label = "root\nset|<r0> r0|<r1> r1",
1335 node [ fillcolor = white, fontcolor = black ];
1339 h2 [ label = "h2\n0|<l> left|<r> right" ];
1340 h3 [ label = "h3\n1|<l> left|<r> right" ];
1341 h4 [ label = "h4\n0|<l> left|<r> right" ];
1342 h5 [ label = "h5\n1|<l> left|<r> right" ];
1343 h6 [ label = "h6\n1|<l> left|<r> right" ];
1346 h2:l -> h4 [ style = invis ];
1347 h2:r -> h5 [ style = invis ];
1349 h3:l -> h2 [ style = invis ];
1357 \caption{TODO, parte 3}
1362 TODO El problema de los ciclos
1366 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1373 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1380 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1387 ----------------------------------------------------------------------------
1389 La cantidad de clasificaciones que pueden hacerse respecto a algoritmos de
1390 recolección de basura son muy grandes. Muchas de estas clasificaciones se
1391 superponen entre sí, habiendo algoritmos que aplican muchas técnicas a la
1392 vez o recolectores híbridos que aplican una técnica para algún tipo de
1393 objeto y otra para otros.
1395 A continuación se enumeran las clasificaciones más importantes que se
1396 pudieron observar en la investigación sobre el `estado del arte`_.
1401 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1403 Generalmente se llama recolección **directa** a aquella en la cual el
1404 compilador o lenguaje instrumenta al *mutator* de forma tal que la
1405 información de conectividad se mantenga activamente cada vez que hay un
1406 cambio en él. Normalmente se utiliza un contador de referencia en cada
1407 celda para este propósito, permitiendo almacenar en todo momento la
1408 cantidad de nodos que apuntan a ésta (dejando de lado el :ref:`problema de
1409 los ciclos <ref_gc_cycles>`). Esto permite reclamar una celda
1410 instantáneamente cuando el *mutator* deja de hacer referencia a ella. Este
1411 tipo de recolectores son, inherentemente :ref:`incrementales <ref_gc_inc>`.
1413 Por el contrario, los recolectores **indirectos** normalmente no
1414 interfieren con el *mutator* en cada actualización del grafo de
1415 conectividad (exceptuando algunos :ref:`recolectores incrementales
1416 <ref_gc_inc>` que a veces necesitan instrumentar el *mutator* pero no para
1417 mantener el estado del grafo de conectividad completo). La recolección se
1418 dispara usualmente cuando el *mutator* requiere alocar memoria pero no hay
1419 más memoria libre conocida disponible y el recolector se encarga de generar
1420 la información de conectividad desde cero para determinar qué celdas son
1423 Estas son las dos grandes familias de recolectores, también conocidos como
1424 `conteo de referencias`_ (directa) y *traicing garbage collection*
1431 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1433 Recolección incremental es aquella que se realiza de forma intercalada con
1434 el *mutator*. En general el propósito es disminuir el tiempo de las pausas
1435 causadas por el recolector (aunque generalmente el resultado es un mayor
1436 costo en tiempo total de recolección).
1438 De los `algoritmos clásicos`_ el único que es incremental en su forma más
1439 básica es el `conteo de referencias`_. Otros recolectores pueden hacerse
1440 incrementales de diversas maneras, pero en general consta de hacer parte
1441 del trabajo de escanear el grafo de conectividad cada vez que el *mutator*
1442 aloca memoria. En general para hacer esto es también necesario instrumentar
1443 al *mutator* de forma tal que informe al recolector cada vez que cambia el
1444 grafo de conectividad, para que éste pueda marcar al sub-grafo afectado por
1445 el cambio como *desactualizado* y así re-escanearlo nuevamente en la
1448 En general la eficiencia de los recolectores incrementales disminuye
1449 considerablemente cuando el *mutator* actualiza muy seguido el grafo de
1450 conectividad, porque debe re-escanear sub-grafos que ya había escaneado una
1451 y otra vez. A esto se debe también que en general el tiempo de
1452 procesamiento total de un recolector incremental sea mayor que uno no
1453 incremental, aunque el tiempo de pausa de una recolección sea menor.
1456 .. _ref_gc_concurrent:
1458 Concurrente / *stop-the-world*
1459 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1461 Los recolectores concurrentes son aquellos que pueden correr en paralelo
1462 con el *mutator*. Por el contrario, aquellos que pausan el *mutator* para
1463 realizar la recolección son usualmente denominados *stop-the-world* (*parar
1464 el mundo*), haciendo referencia a que pausan todos los hilos del *mutator*
1465 para poder escanear el grafo de conectividad de forma consistente.
1467 Para lograr que un recolector sea concurrente generalmente el mecanismo es
1468 similar al necesario para hacer un :ref:`recolector incremental
1469 <ref_gc_inc>`: hay que instrumentar al *mutator* para que informe al
1470 recolector cuando se realiza algún cambio en el grafo de conectividad, de
1471 forma tal que pueda volver a escanear el sub-grafo afectado por el cambio.
1473 Esto también trae como consecuencia el incremento en el tiempo total que
1474 consume el recolector, debido a la necesidad de re-escanear sub-grafos que
1475 han sido modificados.
1480 ----------------------------------------------------------------------------
1482 .. explicar la cantidad de cosas que hay (que son muchas) y dar algunos
1490 1. Instrumentar el *mutator* de forma tal de que informe al recolector cada
1491 vez que cambia el grafo de conectividad, de manera que pueda re-escanear
1492 el sub-grafo que afectado por el cambio.
1494 2. Tomar una foto de la memoria y escanear esa foto estática en vez de la
1495 memoria original, de forma que cualquier cambio en del *mutator* no
1496 afecte la vista de la memoria del recolector.
1498 Ambos métodos tienen ventajas y desventajas. El primero no necesita hacer
1499 una copia costosa de la memoria, pero existe la posibilidad de que el
1500 recolector nunca pueda escanear el grafo de conectividad completo, si el
1501 *mutator* modifica el grafo más rápido de lo que el recolector lo escanea.
1502 Si bien hay formas de evitar esto, es usual que el trabajo de recolección
1503 se vea considerablemente incrementado por la cantidad de veces que hay que
1504 re-escanear el grafo. El segundo método puede ser costoso por las copias
1505 necesarias (aunque esto puede ser mitigado empleando alguna forma de *COW*
1506 [#gccow]_) y existe la posibilidad de que no se recuperen celdas que el
1510 .. [#gccow] *COW* (del inglés *Copy On Write* o *copiar al escribir*) es
1511 una técnica muy utilizada para disminuir las copias innecesarias,
1512 evitando hacer la copia hasta que haya una modificación. Mientras no
1513 hayan modificaciones no es necesario realizar una copia porque todos los
1514 interesados verán la misma información. Esta técnica es utilizada por el
1515 *kernel* de Linux_ por ejemplo, para que varias instancias de un mismo
1516 proceso puedan compartir la memoria.
1519 Boehm, H., and M. Weiser, "Garbage Collection in an Uncooperative
1520 Environment", Software Practice & Experience, September 1988, pp. 807-820.
1521 http://www.hpl.hp.com/personal/Hans_Boehm/spe_gc_paper/preprint.pdf
1522 (http://www.hpl.hp.com/personal/Hans_Boehm/spe_gc_paper/)
1524 Boehm, H., "Space Efficient Conservative Garbage Collection", Proceedings
1525 of the ACM SIGPLAN '93 Conference on Programming Language Design and
1526 Implementation, SIGPLAN Notices 28, 6 (June 1993), pp. 197-206.
1527 http://www.hpl.hp.com/personal/Hans_Boehm/gc/papers/pldi93.ps.Z
1529 Boehm, H., A. Demers, and S. Shenker, "Mostly Parallel Garbage Collection",
1530 Proceedings of the ACM SIGPLAN '91 Conference on Programming Language
1531 Design and Implementation, SIGPLAN Notices 26, 6 (June 1991), pp. 157-164.
1532 http://www.hpl.hp.com/personal/Hans_Boehm/gc/papers/pldi91.ps.Z
1534 Implementación de Bohem GC
1535 http://www.hpl.hp.com/personal/Hans_Boehm/gc/gcdescr.html
1537 Interfaz de Bohem GC
1538 http://www.hpl.hp.com/personal/Hans_Boehm/gc/gcinterface.html
1541 .. include:: links.rst
1543 .. vim: set ts=3 sts=3 sw=3 et tw=75 :