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`` (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*.
825 Se comienza por eliminar la referencia de ``r0`` a ``h1``, que determina
826 que ``h1`` se convirtió en *basura* (figura :vref:`fig:gc-rc-rm-1`). Esto
827 conduce al decremento del contador de ``h2`` y ``h3`` que permanecen en el
828 *live set* ya que sus contadores siguen siendo mayores a 0 (figura
829 :vref:`fig:gc-rc-rm-2`).
832 .. fig:: fig:gc-rc-rm-1
834 Eliminación de la referencia ``r0`` :math:`\to` ``h1`` (parte 1).
838 Estado inicial del grafo de conectividad.
845 edge [ color = gray40 ];
855 subgraph cluster_all {
858 label = "root\nset|<r0> r0|<r1> r1",
864 h1 [ label = "h1\n1|<l> l|<r> r" ];
865 h2 [ label = "h2\n2|<l> l|<r> r" ];
866 h3 [ label = "h3\n2|<l> l|<r> r" ];
867 h4 [ label = "h4\n1|<l> l|<r> r" ];
868 h5 [ label = "h5\n1|<l> l|<r> r" ];
869 h6 [ label = "h6\n1|<l> l|<r> r" ];
884 Al ejecutarse ``update(r0, null)``, se comienza por visitar la celda
892 edge [ color = gray40 ];
902 subgraph cluster_all {
905 label = "root\nset|<r0> r0\n*|<r1> r1",
911 h1 [ label = "h1\n1|<l> l|<r> r" ];
912 h2 [ label = "h2\n2|<l> l|<r> r" ];
913 h3 [ label = "h3\n2|<l> l|<r> r" ];
914 h4 [ label = "h4\n1|<l> l|<r> r" ];
915 h5 [ label = "h5\n1|<l> l|<r> r" ];
916 h6 [ label = "h6\n1|<l> l|<r> r" ];
918 root:r0 -> h1 [ style = bold, color = black ];
931 Se decrementa el contador de ``h1`` quedando en 0 (pasa a ser
932 *basura*). Se elimina primero ``h1.l`` y luego ``h1.r``.
939 edge [ color = gray40 ];
949 subgraph cluster_all {
952 label = "root\nset|<r0> r0\n*|<r1> r1",
959 node [ fillcolor = white, fontcolor = black ];
963 h1 [ label = "h1\n0|<l> l|<r> r" ];
964 h2 [ label = "h2\n2|<l> l|<r> r" ];
965 h3 [ label = "h3\n2|<l> l|<r> r" ];
966 h4 [ label = "h4\n1|<l> l|<r> r" ];
967 h5 [ label = "h5\n1|<l> l|<r> r" ];
968 h6 [ label = "h6\n1|<l> l|<r> r" ];
970 root:r0 -> h1 [ style = invis ];
972 h1:l -> h2 [ style = bold, color = black ];
982 .. fig:: fig:gc-rc-rm-2
984 Eliminación de la referencia ``r0`` :math:`\to` ``h1`` (parte 2).
988 Se decrementa el contador de ``h2`` pero no queda en 0 (permanece en
996 edge [ color = gray40 ];
1006 subgraph cluster_all {
1009 label = "root\nset|<r0> r0\n*|<r1> r1",
1016 node [ fillcolor = white, fontcolor = black ];
1020 h1 [ label = "h1\n0|<l> l|<r> r" ];
1021 h2 [ label = "h2\n1|<l> l|<r> r" ];
1022 h3 [ label = "h3\n2|<l> l|<r> r" ];
1023 h4 [ label = "h4\n1|<l> l|<r> r" ];
1024 h5 [ label = "h5\n1|<l> l|<r> r" ];
1025 h6 [ label = "h6\n1|<l> l|<r> r" ];
1027 root:r0 -> h1 [ style = invis ];
1029 h1:l -> h2 [ style = invis ];
1030 h1:r -> h3 [ style = bold, color = black ];
1040 El contador de ``h3`` tampoco queda en 0, sigue en el *live set*.
1047 edge [ color = gray40 ];
1057 subgraph cluster_all {
1060 label = "root\nset|<r0> r0|<r1> r1",
1067 node [ fillcolor = white, fontcolor = black ];
1071 h1 [ label = "h1\n0|<l> l|<r> r" ];
1072 h2 [ label = "h2\n1|<l> l|<r> r" ];
1073 h3 [ label = "h3\n1|<l> l|<r> r" ];
1074 h4 [ label = "h4\n1|<l> l|<r> r" ];
1075 h5 [ label = "h5\n1|<l> l|<r> r" ];
1076 h6 [ label = "h6\n1|<l> l|<r> r" ];
1078 root:r0 -> h1 [ style = invis ];
1080 h1:l -> h2 [ style = invis ];
1081 h1:r -> h3 [ style = invis ];
1090 Luego se cambia una referencia (en vez de eliminarse) realizándose la
1091 operación ``update(h3.l, h5)``. Para esto primero se incrementa el contador
1092 de referencias de ``h5`` para evitar confundirlo accidentalmente con
1093 *basura* si se elimina alguna celda que apuntaba a ésta. Luego se procede
1094 a decrementar el contador de ``h2`` que queda en 0, transformándose en
1095 *basura* (figura :vref:`fig:gc-rc-up-1`). Lo mismo pasa cuando se desciende
1096 a ``h4``, pero al descender a ``h5`` y decrementar el contador, éste sigue
1097 siendo mayor que 0 (pues ``h3`` va a apuntar a ``h5``) así que permanece en
1098 el *live set*. Finalmente se termina de actualizar la referencia ``h3.l``
1099 para que apunte a ``h5`` (figura :vref:`fig:gc-rc-up-3`).
1102 .. fig:: fig:gc-rc-up-1
1104 Cambio en la referencia ``h2.l`` :math:`\to` ``h2`` a ``h2.l``
1105 :math:`\to` ``h5`` (parte 1).
1109 Comienza ``update(h3.l, h5)``, se incrementa el contador de ``h5``.
1116 edge [ color = gray40 ];
1126 subgraph cluster_all {
1129 label = "root\nset|<r0> r0|<r1> r1",
1136 node [ fillcolor = white, fontcolor = black ];
1140 h1 [ label = "h1\n0|<l> l|<r> r" ];
1141 h2 [ label = "h2\n1|<l> l|<r> r" ];
1142 h3 [ label = "h3\n1|<l> l\n*|<r> r" ];
1143 h4 [ label = "h4\n1|<l> l|<r> r" ];
1144 h5 [ label = "h5\n2|<l> l|<r> r" ];
1145 h6 [ label = "h6\n1|<l> l|<r> r" ];
1147 root:r0 -> h1 [ style = invis ];
1148 h1:l -> h2 [ style = invis ];
1149 h1:r -> h3 [ style = invis ];
1154 h3:l -> h5 [ style = dotted, color = black ];
1161 Luego se procede a visitar las hijas de ``h3``, comenzando por ``h2``.
1168 edge [ color = gray40 ];
1178 subgraph cluster_all {
1181 label = "root\nset|<r0> r0|<r1> r1",
1188 node [ fillcolor = white, fontcolor = black ];
1192 h1 [ label = "h1\n0|<l> l|<r> r" ];
1193 h2 [ label = "h2\n1|<l> l|<r> r" ];
1194 h3 [ label = "h3\n1|<l> l\n*|<r> r" ];
1195 h4 [ label = "h4\n1|<l> l|<r> r" ];
1196 h5 [ label = "h5\n2|<l> l|<r> r" ];
1197 h6 [ label = "h6\n1|<l> l|<r> r" ];
1199 root:r0 -> h1 [ style = invis ];
1200 h1:l -> h2 [ style = invis ];
1201 h1:r -> h3 [ style = invis ];
1205 h3:l -> h2 [ style = bold, color = black ];
1206 h3:l -> h5 [ style = dotted, color = black ];
1213 Se decrementa el contador de ``h2`` y queda en 0 (pasa a ser
1214 *basura*). Se eliminan las referencias a las hijas.
1221 edge [ color = gray40 ];
1231 subgraph cluster_all {
1234 label = "root\nset|<r0> r0|<r1> r1",
1241 node [ fillcolor = white, fontcolor = black ];
1245 h1 [ label = "h1\n0|<l> l|<r> r" ];
1246 h2 [ label = "h2\n1|<l> l|<r> r" ];
1247 h3 [ label = "h3\n1|<l> l\n*|<r> r" ];
1248 h4 [ label = "h4\n1|<l> l|<r> r" ];
1249 h5 [ label = "h5\n2|<l> l|<r> r" ];
1250 h6 [ label = "h6\n1|<l> l|<r> r" ];
1252 root:r0 -> h1 [ style = invis ];
1253 h1:l -> h2 [ style = invis ];
1254 h1:r -> h3 [ style = invis ];
1256 h2:l -> h4 [ style = bold, color = black ];
1258 h3:l -> h2 [ style = invis ];
1259 h3:l -> h5 [ style = dotted, color = black ];
1265 .. fig:: fig:gc-rc-up-2
1267 Cambio en la referencia ``h2.l`` :math:`\to` ``h2`` a ``h2.l``
1268 :math:`\to` ``h5`` (parte 2).
1272 Se decrementa el contador de ``h4`` quedando en 0, pasa a ser
1273 *basura*. Se continúa con ``h5``.
1280 edge [ color = gray40 ];
1290 subgraph cluster_all {
1293 label = "root\nset|<r0> r0|<r1> r1",
1300 node [ fillcolor = white, fontcolor = black ];
1304 h1 [ label = "h1\n0|<l> l|<r> r" ];
1305 h2 [ label = "h2\n1|<l> l|<r> r" ];
1306 h3 [ label = "h3\n1|<l> l\n*|<r> r" ];
1307 h4 [ label = "h4\n0|<l> l|<r> r" ];
1308 h5 [ label = "h5\n2|<l> l|<r> r" ];
1309 h6 [ label = "h6\n1|<l> l|<r> r" ];
1311 root:r0 -> h1 [ style = invis ];
1312 h1:l -> h2 [ style = invis ];
1313 h1:r -> h3 [ style = invis ];
1315 h2:l -> h4 [ style = invis ];
1316 h2:r -> h5 [ style = bold, color = black ];
1317 h3:l -> h2 [ style = invis ];
1318 h3:l -> h5 [ style = dotted, color = black ];
1325 Se decrementa el contador de ``h5`` pero sigue siendo mayor que 0.
1332 edge [ color = gray40 ];
1342 subgraph cluster_all {
1345 label = "root\nset|<r0> r0|<r1> r1",
1352 node [ fillcolor = white, fontcolor = black ];
1356 h1 [ label = "h1\n0|<l> l|<r> r" ];
1357 h2 [ label = "h2\n1|<l> l|<r> r" ];
1358 h3 [ label = "h3\n1|<l> l\n*|<r> r" ];
1359 h4 [ label = "h4\n0|<l> l|<r> r" ];
1360 h5 [ label = "h5\n1|<l> l|<r> r" ];
1361 h6 [ label = "h6\n1|<l> l|<r> r" ];
1363 root:r0 -> h1 [ style = invis ];
1364 h1:l -> h2 [ style = invis ];
1365 h1:r -> h3 [ style = invis ];
1367 h2:l -> h4 [ style = invis ];
1368 h2:r -> h5 [ style = invis ];
1369 h3:l -> h5 [ style = bold, color = black ];
1370 h3:l -> h2 [ style = invis ];
1377 Se termina por actualizar la referencia de ``h3.l`` para que apunte
1385 edge [ color = gray40 ];
1395 subgraph cluster_all {
1398 label = "root\nset|<r0> r0|<r1> r1",
1405 node [ fillcolor = white, fontcolor = black ];
1409 h1 [ label = "h1\n0|<l> l|<r> r" ];
1410 h1 [ label = "h1\n0|<l> l|<r> r" ];
1411 h2 [ label = "h2\n0|<l> l|<r> r" ];
1412 h3 [ label = "h3\n1|<l> l|<r> r" ];
1413 h4 [ label = "h4\n0|<l> l|<r> r" ];
1414 h5 [ label = "h5\n1|<l> l|<r> r" ];
1415 h6 [ label = "h6\n1|<l> l|<r> r" ];
1417 root:r0 -> h1 [ style = invis ];
1418 h1:l -> h2 [ style = invis ];
1419 h1:r -> h3 [ style = invis ];
1421 h2:l -> h4 [ style = invis ];
1422 h2:r -> h5 [ style = invis ];
1424 h3:l -> h2 [ style = invis ];
1431 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1438 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1447 ----------------------------------------------------------------------------
1449 .. explicar la cantidad de cosas que hay (que son muchas) y dar algunos
1455 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1457 La cantidad de clasificaciones que pueden hacerse respecto a algoritmos de
1458 recolección de basura son muy grandes. Muchas de estas clasificaciones se
1459 superponen entre sí, habiendo algoritmos que aplican muchas técnicas a la
1460 vez o recolectores híbridos que aplican una técnica para algún tipo de
1461 objeto y otra para otros.
1463 A continuación se enumeran las clasificaciones más importantes que se
1464 pudieron observar en la investigación sobre el `estado del arte`_.
1471 Generalmente se llama recolección **directa** a aquella en la cual el
1472 compilador o lenguaje instrumenta al *mutator* de forma tal que la
1473 información de conectividad se mantenga activamente cada vez que hay un
1474 cambio en él. Normalmente se utiliza un contador de referencia en cada
1475 celda para este propósito, permitiendo almacenar en todo momento la
1476 cantidad de nodos que apuntan a ésta (dejando de lado el :ref:`problema de
1477 los ciclos <ref_gc_rc_cycles>`). Esto permite reclamar una celda
1478 instantáneamente cuando el *mutator* deja de hacer referencia a ella. Este
1479 tipo de recolectores son, inherentemente :ref:`incrementales <ref_gc_inc>`.
1481 Por el contrario, los recolectores **indirectos** normalmente no
1482 interfieren con el *mutator* en cada actualización del grafo de
1483 conectividad (exceptuando algunos :ref:`recolectores incrementales
1484 <ref_gc_inc>` que a veces necesitan instrumentar el *mutator* pero no para
1485 mantener el estado del grafo de conectividad completo). La recolección se
1486 dispara usualmente cuando el *mutator* requiere alocar memoria pero no hay
1487 más memoria libre conocida disponible y el recolector se encarga de generar
1488 la información de conectividad desde cero para determinar qué celdas son
1491 Estas son las dos grandes familias de recolectores, también conocidos como
1492 `conteo de referencias`_ (directa) y *traicing garbage collection*
1501 Recolección incremental es aquella que se realiza de forma intercalada con
1502 el *mutator*. En general el propósito es disminuir el tiempo de las pausas
1503 causadas por el recolector (aunque generalmente el resultado es un mayor
1504 costo en tiempo total de recolección).
1506 De los `algoritmos clásicos`_ el único que es incremental en su forma más
1507 básica es el `conteo de referencias`_. Otros recolectores pueden hacerse
1508 incrementales de diversas maneras, pero en general consta de hacer parte
1509 del trabajo de escanear el grafo de conectividad cada vez que el *mutator*
1510 aloca memoria. En general para hacer esto es también necesario instrumentar
1511 al *mutator* de forma tal que informe al recolector cada vez que cambia el
1512 grafo de conectividad, para que éste pueda marcar al sub-grafo afectado por
1513 el cambio como *desactualizado* y así re-escanearlo nuevamente en la
1516 En general la eficiencia de los recolectores incrementales disminuye
1517 considerablemente cuando el *mutator* actualiza muy seguido el grafo de
1518 conectividad, porque debe re-escanear sub-grafos que ya había escaneado una
1519 y otra vez. A esto se debe también que en general el tiempo de
1520 procesamiento total de un recolector incremental sea mayor que uno no
1521 incremental, aunque el tiempo de pausa de una recolección sea menor.
1524 .. _ref_gc_concurrent:
1526 Concurrente / *stop-the-world*
1527 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1529 Los recolectores concurrentes son aquellos que pueden correr en paralelo
1530 con el *mutator*. Por el contrario, aquellos que pausan el *mutator* para
1531 realizar la recolección son usualmente denominados *stop-the-world* (*parar
1532 el mundo*), haciendo referencia a que pausan todos los hilos del *mutator*
1533 para poder escanear el grafo de conectividad de forma consistente.
1535 Para lograr que un recolector sea concurrente generalmente el mecanismo es
1536 similar al necesario para hacer un :ref:`recolector incremental
1537 <ref_gc_inc>`: hay que instrumentar al *mutator* para que informe al
1538 recolector cuando se realiza algún cambio en el grafo de conectividad, de
1539 forma tal que pueda volver a escanear el sub-grafo afectado por el cambio.
1541 Esto también trae como consecuencia el incremento en el tiempo total que
1542 consume el recolector, debido a la necesidad de re-escanear sub-grafos que
1543 han sido modificados.
1549 1. Instrumentar el *mutator* de forma tal de que informe al recolector cada
1550 vez que cambia el grafo de conectividad, de manera que pueda re-escanear
1551 el sub-grafo que afectado por el cambio.
1553 2. Tomar una foto de la memoria y escanear esa foto estática en vez de la
1554 memoria original, de forma que cualquier cambio en del *mutator* no
1555 afecte la vista de la memoria del recolector.
1557 Ambos métodos tienen ventajas y desventajas. El primero no necesita hacer
1558 una copia costosa de la memoria, pero existe la posibilidad de que el
1559 recolector nunca pueda escanear el grafo de conectividad completo, si el
1560 *mutator* modifica el grafo más rápido de lo que el recolector lo escanea.
1561 Si bien hay formas de evitar esto, es usual que el trabajo de recolección
1562 se vea considerablemente incrementado por la cantidad de veces que hay que
1563 re-escanear el grafo. El segundo método puede ser costoso por las copias
1564 necesarias (aunque esto puede ser mitigado empleando alguna forma de *COW*
1565 [#gccow]_) y existe la posibilidad de que no se recuperen celdas que el
1569 .. [#gccow] *COW* (del inglés *Copy On Write* o *copiar al escribir*) es
1570 una técnica muy utilizada para disminuir las copias innecesarias,
1571 evitando hacer la copia hasta que haya una modificación. Mientras no
1572 hayan modificaciones no es necesario realizar una copia porque todos los
1573 interesados verán la misma información. Esta técnica es utilizada por el
1574 *kernel* de Linux_ por ejemplo, para que varias instancias de un mismo
1575 proceso puedan compartir la memoria.
1578 Boehm, H., and M. Weiser, "Garbage Collection in an Uncooperative
1579 Environment", Software Practice & Experience, September 1988, pp. 807-820.
1580 http://www.hpl.hp.com/personal/Hans_Boehm/spe_gc_paper/preprint.pdf
1581 (http://www.hpl.hp.com/personal/Hans_Boehm/spe_gc_paper/)
1583 Boehm, H., "Space Efficient Conservative Garbage Collection", Proceedings
1584 of the ACM SIGPLAN '93 Conference on Programming Language Design and
1585 Implementation, SIGPLAN Notices 28, 6 (June 1993), pp. 197-206.
1586 http://www.hpl.hp.com/personal/Hans_Boehm/gc/papers/pldi93.ps.Z
1588 Boehm, H., A. Demers, and S. Shenker, "Mostly Parallel Garbage Collection",
1589 Proceedings of the ACM SIGPLAN '91 Conference on Programming Language
1590 Design and Implementation, SIGPLAN Notices 26, 6 (June 1991), pp. 157-164.
1591 http://www.hpl.hp.com/personal/Hans_Boehm/gc/papers/pldi91.ps.Z
1593 Implementación de Bohem GC
1594 http://www.hpl.hp.com/personal/Hans_Boehm/gc/gcdescr.html
1596 Interfaz de Bohem GC
1597 http://www.hpl.hp.com/personal/Hans_Boehm/gc/gcinterface.html
1600 .. include:: links.rst
1602 .. vim: set ts=3 sts=3 sw=3 et tw=78 :