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 ============================================================================
18 ----------------------------------------------------------------------------
20 *Recolección de basura* se refiere a la recuperación automática de memoria
21 del *heap* [#gcheap]_ una vez que el programa ha dejado de hacer referencia
22 a ella (y por lo tanto, ha dejado de utilizarla).
24 .. [#gcheap] *Heap* es un área de memoria que se caracteriza por ser
25 dinámica (a diferencia del área de memoria estática que está disponible
26 durante toda la ejecución de un programa). Un programa puede reservar
27 memoria en tiempo de ejecución según sea necesario y liberarla cuando ya
28 no la necesita. A diferencia del *stack*, la duración de la *reserva* no
29 está atada a un bloque de código.
31 A medida que el tiempo pasa, cada vez los programas son más complejos y es
32 más compleja la administración de memoria. Uno de los aspectos más
33 importantes de un recolector de basura es lograr un mayor nivel de
34 abstracción y modularidad, dos conceptos claves en la ingeniería de
35 software [JOLI96]_. En particular, al diseñar o programar bibliotecas, de
36 no haber un recolector de basura, **la administración de memoria pasa a ser
37 parte de la interfaz**, lo que produce que los módulos tengan un mayor
38 grado de acoplamiento.
40 Además hay una incontable cantidad de problemas asociados al manejo
41 explícito de memoria que simplemente dejan de existir al utilizar un
42 recolector de basura. Por ejemplo, los errores en el manejo de memoria
43 (como *buffer overflows* [#gcbuff]_ o *dangling pointers* [#gcdang]_) son
44 la causa más frecuente de problemas de seguridad [BEZO06]_.
46 .. [#gcbuff] Un *buffer overflow* (*desbordamiento de memoria* en
47 castellano) se produce cuando se copia un dato a un área de memoria que
48 no es lo suficientemente grande para contenerlo. Esto puede producir que
49 el programa sea abortado por una violación de segmento, o peor,
50 sobreescribir un área de memoria válida, en cuyo caso los resultados son
53 .. [#gcdang] Un *dangling pointer* (*puntero colgante* en castellano) es un
54 puntero que apunta a un área de memoria inválida. Ya sea porque el
55 elemento apuntado no es el mismo tipo o porque la memoria ya ha sido
56 liberada. Al ser desreferenciado, los resultados son impredecibles, el
57 programa podría abortarse por una violación de segmento o podrían pasar
58 peores cosas si el área de memoria fue realocada para almacenar otro
61 La recolección de basura nació junto a Lisp_ a finales de 1950 y en los
62 siguientes años estuvo asociada principalmente a lenguajes funcionales,
63 pero en la actualidad está presente en prácticamente todos los lenguajes de
64 programación, de alto o bajo nivel, aunque sea de forma opcional. En los
65 últimos 10 años tuvo un gran avance, por la adopción en lenguajes de
66 desarrollo rápido utilizados mucho en el sector empresarial, en especial
67 Java_, que fue una plataforma de facto para la investigación y desarrollo
68 de recolectores de basura (aunque no se limitaron a este lenguaje las
71 En las primeras implementaciones de recolectores de basura la penalización
72 en la eficiencia del programa se volvía prohibitiva para muchas
73 aplicaciones. Es por esto que hubo bastante resistencia a la utilización de
74 recolectores de basura, pero el avance en la investigación fue haciendo que
75 cada vez sea una alternativa más viable al manejo manual de memoria,
76 incluso para apliaciones con altos requerimientos de eficiencia. En la
77 actualidad un programa que utiliza un recolector moderno puede ser
78 comparable en eficiencia con uno que utiliza un esquema manual. En
79 particular, si el programa fue diseñado con el recolector de basura en
80 mente en ciertas circunstancias puede ser incluso más eficiente que uno que
81 hace manejo explícito de la memoria. Muchos recolectores mejoran la
82 localidad de referencia [#gcreflocal]_, haciendo que el programa tenga un
83 mejor comportamiento con el caché y la memoria virtual.
85 .. [#gcreflocal] Localidad de referencia es la medida en que los accesos
86 sucesivos de memoria cercana espacialmente son cercanos también en el
87 tiempo. Por ejemplo, un programa que lee todos los elementos de una matriz
88 contigua de una vez o que utiliza la misma variable repetidamente tiene
89 buena localidad referencia. Una buena localidad de referencia interactúa
90 bien con la memoria virtual y caché, ya que reduce el conjunto de trabajo
91 (o *working set*) y mejora la probabildad de éxito (*hit rate*).
93 El recolector de basura debe tener un comportamiento correcto y predecible
94 para que sea útil, si el programador no puede confiar en el recolector
95 de basura, éste se vuelve más un problema que una solución, porque
96 introduce nuevos puntos de falla en los programas, y lo que es peor,
97 puntos de falla no controlados por el programador, volviendo mucho más
98 difícil la búsqueda de errores.
104 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
106 Los programas pueden hacer uso principalmente de 4 áreas de memoria:
109 Se trata de la memoria más básica de una computadora. Es el área de
110 memoria en la que puede operar realmente el procesador, es extremadamente
111 escasa y generalmente su uso es administrado por el lenguaje de
112 programación (o compilador más específicamente). Excepto en situaciones
113 muy particulares, realizando tareas de muy bajo nivel, un programador
114 nunca manipula los registros explícitamente.
116 Área de memoria estática:
117 Es la forma de memoria más simple que un programador utiliza
118 explícitamente. En general las variables globales se almacenan en este
119 área, que es parte inherente del programa y está disponible durante toda
120 su ejecución, por lo tanto nunca cambia su capacidad en tiempo de
121 ejecución. Es la forma más básica de administrar memoria, pero tiene una
122 limitación fundamental: **el tamaño de la memoria tiene que ser conocido
123 en tiempo de compilación**. Los primeros lenguajes de programación solo
124 contaban con este tipo de memoria (además de los registros del
128 Los primeros lenguajes de programación que hicieron uso de una pila
129 aparecieron en el año 1958 (Algol-58 y Atlas Autocode) y fueron los
130 primeros en introducir estructura de bloques, almacenando las
131 variables locales a estos bloques utilizando una pila [JOLI96]_.
132 Esto permite utilizar recursividad y tener un esquema simple de memoria
133 dinámica. Sin embargo este esquema es muy limitado porque el orden de
134 reserva y liberación de memoria tiene que estar bien establecido. Una
135 celda [#gccelda]_ alocada antes que otra nunca puede ser liberada antes
138 .. [#gccelda] En general en la literatura se nombra a una porción de
139 memoria alocada individualmente *celda*, *nodo* u *objeto*
140 indistintamente. En este trabajo se utilizará la misma nomenclatura
141 (haciendo mención explícita cuando alguno de estos términos se
142 refiera a otra cosa, como al nodo de una lista o a un objeto en el
143 sentido de programación orientada a objetos).
146 A diferencia del *stack*, el *heap* provee un área de memoria que puede
147 ser obtenida dinámicamente pero sin limitaciones de orden. Es el tipo de
148 memoria más flexible y por lo tanto el más complejo de administrar; razón
149 por la cual existen los recolectores de basura.
151 La recolección de basura impone algunas restricciones sobre la manera de
152 utilizar el *heap*. Debido a que un recolector de basura debe ser capaz de
153 determinar el grafo de conectividad de la memoria en uso, es necesario que
154 el programa siempre tenga alguna referencia a las celdas activas en los
155 registros, memoria estática o *stack* (normalmente denominado *root set*).
157 Esto implica que una celda sea considerada basura si y sólo si no puede ser
158 alcanzada a través del grafo de conectividad que se comienza a recorrer
159 desde el *root set*. Por lo tanto, una celda está *viva* si y sólo si su
160 dirección de memoria está almacenada en una celda *raíz* (parte del *root
161 set*) o si está almacenada en otra celda *viva* del *heap*.
163 Expresado más formalmente, dada la relación :math:`M \to N`, donde
164 :math:`M` es una celda del *heap* o parte del *root set* y :math:`N` es una
165 celda del *heap*, definida como:
169 M \to N \Longleftrightarrow M \text{ almacena un puntero a } N
171 El conjunto de celdas vivas (o *live set*) queda determinado por:
175 vivas = \left\lbrace N \in Celdas \big/
176 ( \exists r \in Raices / r \to N ) \vee (\exists M \in vivas / M \to N )
179 Cabe aclarar que esta es una definición conceptual, asumiendo que el
180 programa siempre limpia una dirección de memoria almacenada en el *root
181 set* o una celda del *heap* cuando la celda a la que apunta no va a ser
182 utilizada nuevamente. Esto no es siempre cierto y los falsos positivos que
183 esto produce se conoce como un tipo de pérdida de memoria (que es posible
184 incluso al utilizar un recolector de basura) llamada pérdida de memoria
185 *lógica*. Esto puede no ser evitable (incluso cuando el programador no
186 cometa errores) en lenguajes de programación que requieran un recolector de
189 Por último, siendo que el recolector de basura es parte del programa de
190 forma indirecta, es común ver en la literatura que se direfencia entre
191 2 partes del programa, el recolector de basura y el programa en sí. Dado
192 que para el recolector de basura, lo único que interesa conocer del
193 programa en sí son los cambios al grafo de conectividad de las celdas,
194 normalmente se lo llama *mutator* (mutador).
198 .. _ref_gc_intro_mark:
200 Recorrido del grafo de conectividad
201 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
203 El problema de encontrar las celdas *vivas* de un programa se reduce a recorrer
204 un grafo dirigido. El grafo se define como:
210 Donde :math:`V` es el conjunto de vértices, dado por las celdas de memoria
211 y :math:`A` es un conjunto de pares ordenados (aristas), dado por la
212 relación :math:`M \rightarrow N` (es decir, los punteros).
214 El grafo comienza a recorrerse desde el *root set* y todos los vértices que
215 fueron visitados componen el *live set*; el resto de los vértices son
218 Más formalmente, Definimos:
221 secuencia de vértices tal que cada uno de los vértices tiene una arista
222 al próximo vértice en la secuencia. Todo camino finito tiene un *vértice
223 inicial* y un *vértice final* (llamados en conjunto *vértices
224 terminales*). Cualquier vértice no terminal es denominado *vértice
229 \underset{v_1 \rightarrow v_N}{C} = \left\lbrace
230 v_1, \dotsc, v_N \in V \big/ \underset{i \in [1,N-1]}{\forall v_i}
231 \exists (v_i \to v_{i+1}) \in A
235 decimos que :math:`M` está *conectado* a :math:`N` si y sólo si existe un
236 camino de :math:`M` a :math:`N`.
240 M \mapsto N \Longleftrightarrow \exists \underset{M \to N}{C} \in G
243 el conjunto de celdas *vivas* está dado por todos los vértices
244 (:math:`v`) del grafo para los cuales existe una raíz en el *root set*
245 que esté conectada a él.
249 Live \thickspace set = \left\lbrace v \in V \big/
250 \left( \exists r \in Root \thickspace set \big/ r \mapsto v \right)
254 la basura, o celdas *muertas*, quedan determinadas entonces por todas las
255 celdas del *heap* que no son parte del *live set*.
259 Basura = V - Live \thickspace set
261 Esto es, efectivamente, una partición del *heap* (ver figura
262 :vref:`fig:gc-heap-parts`).
265 .. fig:: fig:gc-heap-parts
267 Distintas partes de la memoria, incluyendo relación entre *basura*,
268 *live set*, *heap* y *root set*.
275 node [ shape = record, width = 0, height = 0 ];
277 subgraph cluster_heap {
283 subgraph cluster_live {
296 subgraph cluster_garbage {
301 node [ style = filled, fillcolor = white ];
306 subgraph cluster_root {
311 node [ style = filled, fillcolor = gray96 ];
315 r0 -> h1 -> h2 -> h5;
316 r1 -> h5 -> h6 -> h1;
323 Al proceso de visitar los vértices *conectados* desde el *root set* se lo
324 denomina *marcado*, *fase de marcado* o *mark phase* en inglés, debido
325 a que es necesario marcar los vértices para evitar visitar 2 veces el mismo
326 nodo en casos de que el grafo contenga ciclos [#gccycle]_. De forma similar
327 a la búsqueda, que puede realizarse *primero a lo ancho* (*breadth-first*)
328 o *primero a lo alto* (*depth-first*) del grafo, el marcado de un grafo
329 también puede realizarse de ambas maneras. Cada una podrá o no tener
330 efectos en la eficiencia, en particular dependiendo de la aplicación puede
331 convenir uno u otro método para lograr una mejor localidad de referencia.
333 .. [#gccycle] Un ciclo es un camino donde el *vértice inicial* es el mismo
334 que el *vértice final*. Por lo tanto, los *vértices terminales* son
335 completamente arbitrarios, ya que cualquier *vértice interior* puede ser
336 un *vértice terminal*.
338 Un algoritmo simple (recursivo) de marcado *primero a lo alto* puede ser
339 el siguiente (asumiendo que partimos con todos los vértices sin marcar)
345 for (src, dst) in v.edges
348 function mark_phase() is
349 foreach r in root_set
352 .. [#gcpseudo] Para presentar los algoritmos se utiliza una forma simple de
353 pseudo-código. El pseudo-código se escribe en inglés para que pueda ser
354 más fácilmente contrastado con la literatura, que está en inglés. Para
355 diferenciar posiciones de memoria y punteros de las celdas en sí, se usa
356 la misma sintaxis que C, ``r*`` denota una referencia o puntero y ``*r``
357 denota "objeto al que apunta ``r``\ ". Se sobreentiende que ``r = o``
358 siempre toma la dirección de memoria de ``o``.
360 Una vez concluido el marcado, sabemos que todos los vértices con la marca
361 son parte del *live set* y que todos los vértices no marcados son *basura*.
362 Esto es conocido también como **abstracción bicolor**, dado que en la
363 literatura se habla muchas veces de *colorear* las celdas. En general, una
364 celda sin marcar es de color blanco y una marcada de color negro.
366 Puede observarse un ejemplo del algoritmo en la figura
367 :vref:`fig:gc-mark-1`, en la cual se marca el sub-grafo apuntando por
368 ``r0``. Luego se marca el sub-grafo al que apunta ``r1`` (ver figura
369 :vref:`fig:gc-mark-2`), concluyendo con el marcado del grafo completo,
370 dejando sin marcar solamente las celdas *basura* (en blanco).
373 .. fig:: fig:gc-mark-1
375 Ejemplo de marcado del grafo de conectividad (parte 1).
379 Se comienza a marcar el grafo por la raíz r0.
386 node [ shape = record, width = 0, height = 0];
387 edge [ color = gray40 ];
389 subgraph cluster_all {
392 label = "root\nset|<r0> r0\n*|<r1> r1",
398 node [ style = filled, fillcolor = gray25, fontcolor = white ];
402 root:r0 -> h1 [ style = bold, color = black ];
403 h1 -> h2 -> h5 -> h1;
412 Luego de marcar el nodo ``h1``, se procede al ``h2``.
419 node [ shape = record, width = 0, height = 0 ];
420 edge [ color = gray40 ];
422 subgraph cluster_all {
425 label = "root\nset|<r0> r0\n*|<r1> r1",
431 node [ style = filled, fillcolor = gray25, fontcolor = white ];
435 root:r0 -> h1 [ color = gray10 ];
436 h1 -> h2 [ style = bold, color = black ];
446 Luego sigue el nodo h5.
453 node [ shape = record, width = 0, height = 0 ];
454 edge [ color = gray40 ];
456 subgraph cluster_all {
459 label = "root\nset|<r0> r0\n*|<r1> r1",
465 node [ style = filled, fillcolor = gray25, fontcolor = white ];
469 root:r0 -> h1 [ color = gray10 ];
470 h1 -> h2 [ color = gray10 ];
471 h2 -> h5 [ style = bold, color = black ];
480 .. fig:: fig:gc-mark-2
482 Ejemplo de marcado del grafo de conectividad (parte 2).
486 El nodo h5 tiene una arista al h1, pero el h1 ya fue visitado, por lo
487 tanto no se visita nuevamente.
494 node [ shape = record, width = 0, height = 0 ];
495 edge [ color = gray40 ];
497 subgraph cluster_all {
500 label = "root\nset|<r0> r0\n*|<r1> r1",
506 node [ style = filled, fillcolor = gray25, fontcolor = white ];
510 root:r0 -> h1 [ color = gray10 ];
511 h1 -> h2 [ color = gray10 ];
512 h2 -> h5 [ color = gray10 ];
513 h5 -> h1 [ style = bold, color = black ];
522 Se concluye el marcado del sub-grafo al que conecta r0, se procede
523 a marcar el sub-grafo al que conecta r1, marcando al nodo h6.
530 node [ shape = record, width = 0, height = 0 ];
531 edge [ color = gray40 ];
533 subgraph cluster_all {
536 label = "root\nset|<r0> r0|<r1> r1\n*",
542 node [ style = filled, fillcolor = gray25, fontcolor = white ];
546 root:r0 -> h1 [ color = gray10 ];
547 h1 -> h2 [ color = gray10 ];
548 h2 -> h5 [ color = gray10 ];
549 h5 -> h1 [ color = gray10 ];
550 root:r1 -> h6 [ style = bold, color = black ];
559 El nodo h6 tiene una arista al h2, pero éste ya fue marcado por lo
560 que no se vuelve a visitar. No hay más raíces, se finaliza el marcado
568 node [ shape = record, width = 0, height = 0 ];
569 edge [ color = gray40 ];
571 subgraph cluster_all {
574 label = "root\nset|<r0> r0|<r1> r1\n*",
580 node [ style = filled, fillcolor = gray25, fontcolor = white ];
584 root:r0 -> h1 [ color = gray10 ];
585 h1 -> h2 [ color = gray10 ];
586 h2 -> h5 [ color = gray10 ];
587 h5 -> h1 [ color = gray10 ];
588 root:r1 -> h6 [ color = gray10 ];
589 h6 -> h2 [ style = bold, color = black ];
597 .. _ref_gc_intro_tricolor:
600 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
602 Muchos algoritmos utilizan tres colores para realizar el marcado. El tercer
603 color, gris generalmente, indica que una celda debe ser visitada. Esto
604 permite algoritmos :ref:`concurrentes <ref_gc_concurrent>`
605 e :ref:`incrementales <ref_gc_inc>`, además de otro tipo de optimizaciones.
606 Entonces, lo que plantea esta abtracción es una nueva partición del heap al
607 momento de marcar, esta vez son 3 porciones: blanca, gris y negra.
609 Al principio todas las celdas se pintan de blanco, excepto el *root set*
610 que se punta de gris. Luego se van obteniendo celdas del conjunto de las
611 grises y se las pinta de negro, pintando sus hijas directas de gris.
613 Una vez que no hay más celdas grises, tenemos la garantía de que las celdas
614 negras serán el *live set* y las celdas blancas *basura*. Esto se debe
615 a que siempre se mantiene esta invariante: **ninguna celda negra apunta
616 directamente a una celda blanca**. Las celdas blancas siempre son apuntadas
617 por celdas blancas o grises. Entonces, siempre que el conjunto de celdas
618 grises sea vacío, no habrán celdas negras conectadas a blancas, siendo las
619 celdas blancas *basura*.
621 El algoritmo básico para marcar con tres colores es el siguiente (asumiendo
622 que todas las celdas parten pintadas de blanco, es decir, el conjunto
623 blanco contiene todas las celdas de memoria y los conjuntos negro y gris
626 function mark_phase() is
627 foreach r in root_set
629 while not gray_set.empty()
632 for (src, dst) in v.edges
637 Es simple notar que este algoritmo es naturalmente no recursivo, lo que de
638 por sí ya presenta una ventaja sobre el marcado *bicolor*.
642 .. _ref_gc_intro_services:
645 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
647 En general todos los algoritmos de recolección de basura utilizan servicios
648 de una capa inferior [#gclowlayer]_ y proveen servicios a una capa superior
651 .. [#gclowlayer] En general estos servicios están provistos directamente
652 por el sistema operativo pero también pueden estar dados por un
653 administrador de memoria de bajo nivel (o *low level allocator* en
656 .. [#gchilayer] En general estos servicios son utilizados directamente por
657 el lenguaje de programación, pero pueden ser utilizados directamente por
658 el usuario del lenguaje si éste interatúa con el recolector, ya sea por
659 algún requerimiento particular o porque el lenguaje no tiene soporte
660 diercto de recolección de basura y el recolector está implementado como
661 una biblioteca de usuario.
663 A continuación se presentan las primitivas en común que utilizan todos los
664 recolectores a lo largo de este documento.
666 Servicios utilizados por el recolector son los siguientes:
668 :math:`alloc() \to cell`:
669 obtiene una nueva celda de memoria. El mecanismo por el cual se obtiene
670 la celda es indistinto para esta sección, puede ser de una lista libre,
671 puede ser de un administrador de memoria de más bajo nivel provisto por
672 el sistema operativo o la biblioteca estándar de C (``malloc()``), etc.
673 Cómo organizar la memoria es un área de investigación completa y si bien
674 está estrechamente relacionada con la recolección de basura, en este
675 trabajo no se prestará particular atención a este aspecto (salvo casos
676 donde el recolector impone una cierta organización de memoria en el *low
677 level allocator*). Por simplicidad también asumiremos (a menos que se
678 indique lo contrario) que las celdas son de tamaño fijo. Esta restricción
679 normalmente puede ser fácilmente relajada (en los recolectores que la
683 libera una celda que ya no va a ser utilizada. La celda liberada debe
684 haber sido obtenida mediante ``alloc()``.
686 Y los servicios básicos proporcionados por el recolector son los
689 :math:`new() \to cell`:
690 obtiene una celda de memoria para ser utilizada por el programa.
692 :math:`update(ref, cell)`:
693 notifica al recolector que la referencia :math:`ref` ahora apunta
694 a :math:`cell`. Visto más formalmente, sería análogo a decir que hubo un
695 cambio en la conectividad del grafo: la arista :math:`src \to old` cambia
696 por :math:`src \to new` (donde :math:`src` es la celda que contiene la
697 referencia :math:`ref`, :math:`old` es la celda a la que apunta la
698 referencia :math:`ref` y :math:`new` es el argumento :math:`cell`). Si
699 :math:`cell` es ``null``, sería análogo a informar que se elimina la
700 arista :math:`src \to old`.
703 este servicio, según el algoritmo, puede ser utilizado para informar un
704 cambio en la conectividad del grafo, la eliminación de una arista
705 (análogo a :math:`update(ref, null)` pero sin proporcionar información
706 sobre la arista a eliminar). Esto es generalmente útil solo en
707 :ref:`conteo de referencias <ref_gc_rc>`. Para otros recolectores puede
708 significar que el usuario asegura que no hay más referencias a esta
709 celda, es decir, análogo a eliminar el conjunto de aristas
710 :math:`\big\lbrace (v, w) \in A , v \in Live \thickspace set , w \in
711 Live \thickspace set \big/ w = cell`.
714 indica al recolector que debe hacer un análisis del grafo de conectividad
715 en busca de *basura*. Generalmente este servicio es invocado por el
716 propio recolector cuando no hay más celdas reciclables.
718 No todos los servicios son implementados por todos los recolectores, pero
719 son lo suficientemente comunes como para describirlos de forma general en
720 esta sección. Algunos son principalmente ideados para uso interno del
721 recolector, aunque en ciertas circunstancias pueden ser utilizados por el
729 ----------------------------------------------------------------------------
731 En la literatura se encuentran normalmente referencias a 3 algoritmos
732 clásicos, que son utilizados generalmente como bloques básicos para
733 construir recolectores más complejos. Se presentan las versiones históricas
734 más simples a fin de facilitar la comprensión conceptual.
740 Conteo de referencias
741 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
743 Se trata del algoritmo más antiguo de todos, implementado por primera vez
744 por `John McCarthy`_ para Lisp_ a finales de 1950. Se trata de un método
745 :ref:`directo <ref_gc_direct>` e :ref:`incremental <ref_gc_inc>` por
746 naturaleza, ya que distribuye la carga de la recolección de basura durante
747 toda la ejecución del programa, cada vez que el *mutator* cambia la
748 conectividad de algún nodo del grafo de conectividad.
750 El método consiste en tener un contador asociado a cada celda que contenga
751 la cantidad de celdas **vivas** que apuntan a ésta. Es decir, es la
752 cardinalidad del conjunto de aristas que tienen por destino a la celda.
753 Formalmente podemos definir el contador :math:`rc(v)` (de *reference
754 counter* en inglés) de la siguiente manera:
760 (v_1, v_2) \in A \big/
761 v_1 \in Live \thickspace set \cup Root \thickspace set
766 El *mutator* entonces debe actualizar este contador cada vez que el grafo
767 de conectividad cambia, es decir, cada vez que se agrega, modifica
768 o elimina una arista del grafo (o visto de una forma más cercana al código,
769 cada vez que se agrega, modifica o elimina un puntero).
771 Esta invariante es fundamental para el conteo de referencias, porque se
772 asume que si el contador es 0 entonces el *mutator* no tiene ninguna
773 referencia a la celda y por lo tanto es *basura*:
777 rc(v) = 0 \Rightarrow v \in Basura
779 Para mantener esta invariante el *mutator*, cada vez que cambia un puntero
780 debe decrementar en 1 el contador de la celda a la que apuntaba
781 antiguamente e incrementar en 1 el contador de la celda a la que apunta
782 luego de la modificación. Esto asegura que la invariante se mantenga
783 durante toda la ejecución del programa. Si al momento de decrementar un
784 contador éste queda en 0, la celda asociada debe liberarse de forma de
785 poder ser reciclada. Esto implica que si esta celda almacena punteros, los
786 contadores de las celdas apuntadas deben ser decrementados también, porque
787 solo deben almacenarse en el contador las aristas del *live set* para
788 mantener la invariante. De esto puede resultar que otros contadores de
789 referencia queden en 0 y más celdas sean liberadas. Por lo tanto,
790 teóricamente la complejidad de eliminar una referencia puede ser
791 :math:`O(\lvert Live \thickspace set \rvert)` en el peor caso.
793 Las primitivas implementadas para este tipo de recolector son las
794 siguientes (acompañadas de una implementación básica)::
803 function del(cell) is
804 cell.rc = cell.rc - 1
806 foreach child* in cell.children
810 function update(ref*, cell) is
811 cell.rc = cell.rc + 1
817 .. _ref_gc_rc_cycles:
822 El conteo de referencias tiene, sin embargo, un problema fundamental:
823 **falla con estructuras cíclicas**. Esto significa que siempre que haya un
824 ciclo en el grafo de conectividad, hay una pérdida de memoria potencial en
825 el programa. Un ciclo es un camino :math:`\underset{v \to v}{C}`, es decir,
826 el *vértice inicial* es el mismo que el *vértice final*.
828 Cuando esto sucede, las celdas que participan del ciclo tienen siempre su
829 contador mayor que 0, sin embargo puede no haber ningún elemento del *root
830 set* que apunte a una celda dentro del ciclo, por lo tanto el ciclo es
831 *basura* (al igual que cualquier otra celda que sea referenciada por el
832 ciclo pero que no tenga otras referencias externas) y sin embargo los
833 contadores no son 0. Los ciclos, por lo tanto, *rompen* la invariante del
834 conteo de referencia.
836 Hay formas de solucionar esto, pero siempre recaen en un esquema que va por
837 fuera del conteo de referencias puro. En general los métodos para
838 solucionar esto son variados y van desde realizar un marcado del subgrafo
839 para detectar nodos hasta tener otro recolector completo de *emergencia*,
840 pasando por tratar los ciclos como un todo contar las referencias al ciclo
841 completo en vez de a cada celda en particular.
843 Incluso con este problema, el conteo de referencia sin ningún tipo de
844 solución en cuanto a la detección y recolección de ciclos fue utilizado en
845 muchos lenguajes de programación sin que su necesidad sea tan evidente. Por
846 ejemplo Python_ agregó recolección de ciclos en la versión 2.0 [NAS00]_
847 (liberada en octubre de 2000) y PHP_ recién agrega detección de ciclos en
848 la versión 5.3 (todavía no liberada al momento de escribir este documento)
852 .. _ref_gc_rc_example:
857 A continuación se presenta un ejemplo gráfico para facilitar la comprensión
858 del algoritmo. Por simplicidad se asumen celdas de tamaño fijo con dos
859 punteros, ``left`` (``l``) y ``right`` (``r``) y se muestra el contador de
860 referencias abajo del nombre de cada celda. Se parte con una pequeña
861 estructura ya construída y se muestra como opera el algoritmo al eliminar
862 o cambiar una referencia (cambios en la conectividad del grafo). En un
863 comienzo todas las celdas son accesibles desde el *root set* por lo tanto
864 son todas parte del *live set*.
866 Se comienza por eliminar la referencia de ``r0`` a ``h1``, que determina
867 que ``h1`` se convirtió en *basura* (ver figura :vref:`fig:gc-rc-rm-1`). Esto
868 conduce al decremento del contador de ``h2`` y ``h3`` que permanecen en el
869 *live set* ya que sus contadores siguen siendo mayores a 0 (ver figura
870 :vref:`fig:gc-rc-rm-2`).
872 .. fig:: fig:gc-rc-rm-1
874 Eliminación de la referencia ``r0`` :math:`\to` ``h1`` (parte 1).
878 Estado inicial del grafo de conectividad.
885 edge [ color = gray40 ];
895 subgraph cluster_all {
898 label = "root\nset|<r0> r0|<r1> r1",
904 h1 [ label = "h1\n1|<l> l|<r> r" ];
905 h2 [ label = "h2\n2|<l> l|<r> r" ];
906 h3 [ label = "h3\n3|<l> l|<r> r" ];
907 h4 [ label = "h4\n1|<l> l|<r> r" ];
908 h5 [ label = "h5\n1|<l> l|<r> r" ];
909 h6 [ label = "h6\n1|<l> l|<r> r" ];
925 Al ejecutarse ``update(r0, null)``, se comienza por visitar la celda
933 edge [ color = gray40 ];
943 subgraph cluster_all {
946 label = "root\nset|<r0> r0\n*|<r1> r1",
952 h1 [ label = "h1\n1|<l> l|<r> r" ];
953 h2 [ label = "h2\n2|<l> l|<r> r" ];
954 h3 [ label = "h3\n3|<l> l|<r> r" ];
955 h4 [ label = "h4\n1|<l> l|<r> r" ];
956 h5 [ label = "h5\n1|<l> l|<r> r" ];
957 h6 [ label = "h6\n1|<l> l|<r> r" ];
959 root:r0 -> h1 [ style = bold, color = black ];
973 Se decrementa el contador de ``h1`` quedando en 0 (pasa a ser
974 *basura*). Se elimina primero ``h1.l`` y luego ``h1.r``.
981 edge [ color = gray40 ];
991 subgraph cluster_all {
994 label = "root\nset|<r0> r0\n*|<r1> r1",
1001 node [ fillcolor = white, fontcolor = black ];
1005 h1 [ label = "h1\n0|<l> l|<r> r" ];
1006 h2 [ label = "h2\n2|<l> l|<r> r" ];
1007 h3 [ label = "h3\n3|<l> l|<r> r" ];
1008 h4 [ label = "h4\n1|<l> l|<r> r" ];
1009 h5 [ label = "h5\n1|<l> l|<r> r" ];
1010 h6 [ label = "h6\n1|<l> l|<r> r" ];
1012 root:r0 -> h1 [ style = invis ];
1014 h1:l -> h2 [ style = bold, color = black ];
1025 .. fig:: fig:gc-rc-rm-2
1028 Eliminación de la referencia ``r0`` :math:`\to` ``h1`` (parte 2).
1032 Se decrementa el contador de ``h2`` pero no queda en 0 (permanece en
1040 edge [ color = gray40 ];
1050 subgraph cluster_all {
1053 label = "root\nset|<r0> r0\n*|<r1> r1",
1060 node [ fillcolor = white, fontcolor = black ];
1064 h1 [ label = "h1\n0|<l> l|<r> r" ];
1065 h2 [ label = "h2\n1|<l> l|<r> r" ];
1066 h3 [ label = "h3\n3|<l> l|<r> r" ];
1067 h4 [ label = "h4\n1|<l> l|<r> r" ];
1068 h5 [ label = "h5\n1|<l> l|<r> r" ];
1069 h6 [ label = "h6\n1|<l> l|<r> r" ];
1071 root:r0 -> h1 [ style = invis ];
1073 h1:l -> h2 [ style = invis ];
1074 h1:r -> h3 [ style = bold, color = black ];
1085 El contador de ``h3`` tampoco queda en 0, sigue en el *live set*.
1092 edge [ color = gray40 ];
1102 subgraph cluster_all {
1105 label = "root\nset|<r0> r0|<r1> r1",
1112 node [ fillcolor = white, fontcolor = black ];
1116 h1 [ label = "h1\n0|<l> l|<r> r" ];
1117 h2 [ label = "h2\n1|<l> l|<r> r" ];
1118 h3 [ label = "h3\n2|<l> l|<r> r" ];
1119 h4 [ label = "h4\n1|<l> l|<r> r" ];
1120 h5 [ label = "h5\n1|<l> l|<r> r" ];
1121 h6 [ label = "h6\n1|<l> l|<r> r" ];
1123 root:r0 -> h1 [ style = invis ];
1125 h1:l -> h2 [ style = invis ];
1126 h1:r -> h3 [ style = invis ];
1136 Luego se cambia una referencia (en vez de eliminarse) realizándose la
1137 operación ``update(h3.l, h5)``. Para esto primero se incrementa el contador
1138 de referencias de ``h5`` para evitar confundirlo accidentalmente con
1139 *basura* si se elimina alguna celda que apuntaba a ésta. Luego se procede
1140 a decrementar el contador de ``h2`` que queda en 0, transformándose en
1141 *basura* (ver figura :vref:`fig:gc-rc-up-1`).
1143 .. fig:: fig:gc-rc-up-1
1145 Cambio en la referencia ``h2.l`` :math:`\to` ``h2`` a ``h2.l``
1146 :math:`\to` ``h5`` (parte 1).
1150 Comienza ``update(h3.l, h5)``, se incrementa el contador de ``h5``.
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\n2|<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 ];
1195 h3:l -> h5 [ style = dotted, color = black ];
1203 Luego se procede a visitar las hijas de ``h3``, comenzando por ``h2``.
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\n2|<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 ];
1247 h3:l -> h2 [ style = bold, color = black ];
1248 h3:l -> h5 [ style = dotted, color = black ];
1256 Se decrementa el contador de ``h2`` y queda en 0 (pasa a ser
1257 *basura*). Se eliminan las referencias a las hijas.
1264 edge [ color = gray40 ];
1274 subgraph cluster_all {
1277 label = "root\nset|<r0> r0|<r1> r1",
1284 node [ fillcolor = white, fontcolor = black ];
1288 h1 [ label = "h1\n0|<l> l|<r> r" ];
1289 h2 [ label = "h2\n1|<l> l|<r> r" ];
1290 h3 [ label = "h3\n2|<l> l\n*|<r> r" ];
1291 h4 [ label = "h4\n1|<l> l|<r> r" ];
1292 h5 [ label = "h5\n2|<l> l|<r> r" ];
1293 h6 [ label = "h6\n1|<l> l|<r> r" ];
1295 root:r0 -> h1 [ style = invis ];
1296 h1:l -> h2 [ style = invis ];
1297 h1:r -> h3 [ style = invis ];
1299 h2:l -> h4 [ style = bold, color = black ];
1301 h3:l -> h2 [ style = invis ];
1302 h3:l -> h5 [ style = dotted, color = black ];
1309 Lo mismo pasa cuando se desciende a ``h4``, pero al descender a ``h5``
1310 y decrementar el contador, éste sigue siendo mayor que 0 (pues ``h3`` va
1311 a apuntar a ``h5``) así que permanece en el *live set*. Finalmente se termina
1312 de actualizar la referencia ``h3.l`` para que apunte a ``h5`` (ver figura
1313 :vref:`fig:gc-rc-up-2`).
1315 .. fig:: fig:gc-rc-up-2
1317 Cambio en la referencia ``h2.l`` :math:`\to` ``h2`` a ``h2.l``
1318 :math:`\to` ``h5`` (parte 2).
1322 Se decrementa el contador de ``h4`` quedando en 0, pasa a ser
1323 *basura*. Se continúa con ``h5``.
1330 edge [ color = gray40 ];
1340 subgraph cluster_all {
1343 label = "root\nset|<r0> r0|<r1> r1",
1350 node [ fillcolor = white, fontcolor = black ];
1354 h1 [ label = "h1\n0|<l> l|<r> r" ];
1355 h2 [ label = "h2\n1|<l> l|<r> r" ];
1356 h3 [ label = "h3\n2|<l> l\n*|<r> r" ];
1357 h4 [ label = "h4\n0|<l> l|<r> r" ];
1358 h5 [ label = "h5\n2|<l> l|<r> r" ];
1359 h6 [ label = "h6\n1|<l> l|<r> r" ];
1361 root:r0 -> h1 [ style = invis ];
1362 h1:l -> h2 [ style = invis ];
1363 h1:r -> h3 [ style = invis ];
1365 h2:l -> h4 [ style = invis ];
1366 h2:r -> h5 [ style = bold, color = black ];
1367 h3:l -> h2 [ style = invis ];
1368 h3:l -> h5 [ style = dotted, color = black ];
1376 Se decrementa el contador de ``h5`` pero sigue siendo mayor que 0.
1383 edge [ color = gray40 ];
1393 subgraph cluster_all {
1396 label = "root\nset|<r0> r0|<r1> r1",
1403 node [ fillcolor = white, fontcolor = black ];
1407 h1 [ label = "h1\n0|<l> l|<r> r" ];
1408 h2 [ label = "h2\n1|<l> l|<r> r" ];
1409 h3 [ label = "h3\n2|<l> l\n*|<r> r" ];
1410 h4 [ label = "h4\n0|<l> l|<r> r" ];
1411 h5 [ label = "h5\n1|<l> l|<r> r" ];
1412 h6 [ label = "h6\n1|<l> l|<r> r" ];
1414 root:r0 -> h1 [ style = invis ];
1415 h1:l -> h2 [ style = invis ];
1416 h1:r -> h3 [ style = invis ];
1418 h2:l -> h4 [ style = invis ];
1419 h2:r -> h5 [ style = invis ];
1420 h3:l -> h5 [ style = bold, color = black ];
1421 h3:l -> h2 [ style = invis ];
1429 Se termina por actualizar la referencia de ``h3.l`` para que apunte
1437 edge [ color = gray40 ];
1447 subgraph cluster_all {
1450 label = "root\nset|<r0> r0|<r1> r1",
1457 node [ fillcolor = white, fontcolor = black ];
1461 h1 [ label = "h1\n0|<l> l|<r> r" ];
1462 h1 [ label = "h1\n0|<l> l|<r> r" ];
1463 h2 [ label = "h2\n0|<l> l|<r> r" ];
1464 h3 [ label = "h3\n2|<l> l|<r> r" ];
1465 h4 [ label = "h4\n0|<l> l|<r> r" ];
1466 h5 [ label = "h5\n1|<l> l|<r> r" ];
1467 h6 [ label = "h6\n1|<l> l|<r> r" ];
1469 root:r0 -> h1 [ style = invis ];
1470 h1:l -> h2 [ style = invis ];
1471 h1:r -> h3 [ style = invis ];
1473 h2:l -> h4 [ style = invis ];
1474 h2:r -> h5 [ style = invis ];
1476 h3:l -> h2 [ style = invis ];
1483 Finalmente se presenta lo que sucede cuando se elimina la última referencia
1484 a un ciclo (en este caso un ciclo simple de 2 celdas: ``h3`` y ``h6``). Se
1485 elimina la única referencia externa al ciclo (``r1``), por lo que se visita
1486 la celda ``h3`` decrementando su contador de referencias, pero éste
1487 continúa siendo mayor que 0 porque la celda ``h6`` (parte del ciclo) la
1488 referencia. Por lo tanto el ciclo, y todas las celdas a las que apunta que
1489 no tienen otras referencias externas y por lo tanto deberían ser *basura*
1490 también (``h5``), no pueden ser recicladas y su memoria es perdida (ver
1491 figura :vref:`fig:gc-rc-cycle`).
1493 .. fig:: fig:gc-rc-cycle
1496 Eliminación de la referencia ``r1`` :math:`\to` ``h3`` (pérdida de
1497 memoria debido a un ciclo).
1501 El ejecutarse ``update(r1, null)`` se visita la celda ``h3``.
1508 edge [ color = gray40 ];
1518 subgraph cluster_all {
1521 label = "root\nset|<r0> r0|<r1> r1\n*",
1528 node [ fillcolor = white, fontcolor = black ];
1532 h1 [ label = "h1\n0|<l> l|<r> r" ];
1533 h1 [ label = "h1\n0|<l> l|<r> r" ];
1534 h2 [ label = "h2\n0|<l> l|<r> r" ];
1535 h3 [ label = "h3\n2|<l> l|<r> r" ];
1536 h4 [ label = "h4\n0|<l> l|<r> r" ];
1537 h5 [ label = "h5\n1|<l> l|<r> r" ];
1538 h6 [ label = "h6\n1|<l> l|<r> r" ];
1540 root:r0 -> h1 [ style = invis ];
1541 h1:l -> h2 [ style = invis ];
1542 h1:r -> h3 [ style = invis ];
1543 root:r1 -> h3 [ style = bold, color = black ];
1544 h2:l -> h4 [ style = invis ];
1545 h2:r -> h5 [ style = invis ];
1547 h3:l -> h2 [ style = invis ];
1555 Se decrementa el contador de ``h3`` pero sigue siendo mayor que 0 por
1563 edge [ color = gray40 ];
1573 subgraph cluster_all {
1576 label = "root\nset|<r0> r0|<r1> r1\n*",
1583 node [ fillcolor = white, fontcolor = black ];
1587 h1 [ label = "h1\n0|<l> l|<r> r" ];
1588 h1 [ label = "h1\n0|<l> l|<r> r" ];
1589 h2 [ label = "h2\n0|<l> l|<r> r" ];
1590 h3 [ label = "h3\n1|<l> l|<r> r" ];
1591 h4 [ label = "h4\n0|<l> l|<r> r" ];
1592 h5 [ label = "h5\n1|<l> l|<r> r" ];
1593 h6 [ label = "h6\n1|<l> l|<r> r" ];
1595 root:r0 -> h1 [ style = invis ];
1596 h1:l -> h2 [ style = invis ];
1597 h1:r -> h3 [ style = invis ];
1598 root:r1 -> h3 [ style = invis ];
1599 h2:l -> h4 [ style = invis ];
1600 h2:r -> h5 [ style = invis ];
1602 h3:l -> h2 [ style = invis ];
1610 .. _ref_gc_mark_sweep:
1613 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1615 Este algoritmo es el más parecido a la teoría sobre recolección de basura.
1616 Consiste en realizar la recolección en 2 fases: marcado y barrido. La primera
1617 fase consiste en el proceso de marcar el grafo de conectividad del *heap* para
1618 descubrir qué celdas son alcanzables desde el *root set*, tal y como se
1619 describió en :ref:`ref_gc_intro_mark`.
1621 Una vez marcadas todas las celdas, se sabe que las celdas *blancas* son
1622 *basura*, por lo tanto el paso que queda es el *barrido* de estas celdas,
1623 liberándolas. Esto se efectúa recorriendo todo el *heap*. Por lo tanto cada
1624 recolección es :math:`O(\lvert Heap \rvert)`, a diferencia del conteo de
1625 referencia que dijimos que en el peor caso es :math:`O(\lvert Live \thickspace
1626 set \rvert)`. Sin embargo el conteo de referencias se ejecuta **cada vez que
1627 se actualiza una referencia** mientras que la recolección en el marcado
1628 y barrido se realiza típicamente solo cuando el *mutator* pide una celda pero
1629 no hay ninguna libre. Esto hace que la constante del conteo de referencias sea
1630 típicamente varios órdenes de magnitud mayores que en el marcado y barrido.
1632 A continuación se presentan los servicios básicos de este algoritmo::
1643 function collect() is
1647 function sweep_phase() is
1648 foreach cell in heap
1654 El algoritmo ``mark_sweep()`` es exactamente igual al presentado en
1655 :ref:`ref_gc_intro_mark`. Es preciso notar que la fase de barrido
1656 (``sweep_phase()``) debe tener una comunicación extra con el *low level
1657 allocator* para poder obtener todas las celdas de memoria que existen en el
1660 A diferencia del conteo de referencias, este algoritmo es :ref:`indirecto
1661 <ref_gc_direct>` y :ref:`no incremental <ref_gc_inc>`, ya que se realiza un
1662 recorrido de todo el *heap* de forma espaciada a través de la ejecución del
1663 programa. En general el *mutator* sufre pausas considerablemente mayores (en
1664 promedio) que con el conteo de referencias, lo que puede ser problemático para
1665 aplicaciones con requerimientos rígidos de tiempo, como aplicaciones
1666 *real-time*. Debido a la percepción de las pausas grandes, este tipo de
1667 colectores se conocen como :ref:`stop-the-world <ref_gc_concurrent>` (o
1668 *detener el mundo*).
1670 Una ventaja fundamental sobre el conteo de referencias es la posibilidad de
1671 reclamar estructuras cíclicas sin consideraciones especiales. Podemos observar
1672 como esto es posible analizando el ejemplo en las figuras
1673 :r:`fig:gc-mark-1` y :vref:`fig:gc-mark-2`. Si se eliminaran las referencias
1674 :math:`r0 \to h1` y :math:`h6 \to h2`, la fase de marcado consistiría
1675 solamente en marcar la celda :math:`h6`, pues es la única alcanzable desde el
1676 *root set*. Todas las demás celdas permanecerían blancas y por lo tanto pueden
1677 ser liberadas sin inconvenientes en la fase de barrido, que recorre el *heap*
1684 Copia de semi-espacio
1685 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1687 Este algoritmo consiste en hacer una partición del *heap* en 2 mitades
1688 o *semi-espacios*, llamados usualmente *Fromspace* y *Tospace*. El primero se
1689 utiliza para alocar nuevas celdas de forma lineal, asumiendo un *heap*
1690 contiguo, incrementando un puntero (ver figura :vref:`fig:gc-copy`). Esto se
1691 conoce como *pointer bump allocation* y es, probablemente, la forma más
1692 eficiente de alocar memoria (tan eficiente como alocar memoria en el *stack*).
1694 .. fig:: fig:gc-copy
1696 Estructura del *heap* de un recolector con copia de semi-espacios.
1702 zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
1704 /---+"Fromspace" /---+"Tospace"
1706 V_______________________________V_______________________________
1707 | XXXX X XXX aaaaaaaaaaaaaaaa|bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb|
1708 | XXXX X XXX aaaaaaaaaaaaaaaa|bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb|
1709 | XXXX X XXX aaaaaaaaaaaaaaaa|bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb|
1710 |~~~~~~~~~~~~~~~A~~~~~~~~~~~~~~~|~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1712 | | | XX "Fromspace usado"
1714 | | ZZ "Fromspace basura"
1716 |/ "longitud del semi-espacio" |/ AA "Fromspace libre"
1717 +- - - - - - - - - - - - - - - -+
1721 La segunda mitad (*Tospace*) permanece inutilizada hasta que se agota el
1722 espacio en el *Fromspace*; en ese momento comienza el proceso de recolección
1723 de basura que consiste en recorrer el grafo de conectividad, copiando las
1724 celdas *vivas* del *Fromspace* al *Tospace* de manera contigua, como si
1725 estuvieran alocando por primera vez. Como la posición en memoria de las celdas
1726 cambia al ser movidas, es necesario actualizar la dirección de memoria de
1727 todas las celdas *vivas*. Para esto se almacena una dirección de memoria de
1728 redirección, *forwarding address*, en las celdas que mueven. La *forwarding
1729 address* sirve a su vez de marca, para no recorrer una celda dos veces (como
1730 se explica en :ref:`ref_gc_intro_mark`). Cuando se encuentra una celda que ya
1731 fue movida, simplemente se actualiza la referencia por la cual se llegó a esa
1732 celda para que apunte a la nueva dirección, almacenada en la *forwarding
1733 address*. Una vez finalizado este proceso, el *Fromspace* y *Tospace*
1734 invierten roles y se prosigue de la misma manera (todo lo que quedó en el
1735 viejo *Fromspace* es *basura* por definición, por lo que se convierte el
1738 A continuación se presenta una implementación sencilla de los servicios
1739 provistos por este tipo de recolectores. Cabe destacar que este tipo de
1740 recolectores deben estar íntimamente relacionados con el *low level
1741 allocator*, ya que la organización del *heap* y la forma de alocar
1742 memoria es parte fundamental de este algoritmo. Se asume que ya hay dos áreas
1743 de memoria del mismo tamaño destinadas al *Fromspace* y *Tospace*, y la
1744 existencia de 4 variables: ``fromspace`` (que apunta a la base del
1745 *Fromspace*), ``tospace`` (que apunta a la base del *Tospace*), ``spacesize``
1746 (que contiene el tamaño de un semi-espacio) y ``free`` (que apunta al lugar
1747 del *Fromspace* donde comienza la memoria libre). También vale aclarar que
1748 este algoritmo soporta inherentemente celdas de tamaño variable, por lo que
1749 los servicios ``alloc()`` y ``new()`` [#gccopynew]_ reciben como parámetro el
1750 tamaño de la celda a alocar::
1752 function alloc(size) is
1753 if free + size > fromspace + spacesize
1760 function new(size) is
1769 function collect() is
1771 foreach r in root_set
1773 fromspace, tospace = tospace, fromspace
1775 function copy(cell) is
1776 if cell.forwarding_address is null
1777 cell.forwarding_address = free
1778 free = free + cell.size
1779 foreach child in cell
1781 return cell.forwarding_address
1783 return cell.forwarding_address
1785 .. [#gccopynew] Notar que ``new()`` es igual que en el marcado y barrido con
1786 la salvedad de que en este caso toma como parámetro el tamaño de la celda.
1788 Esta técnica tiene nombres variados en inglés: *semi-space*, *two-space*
1789 o simplemente *copying collector*. En este documento se denomina "copia de
1790 semi-espacio" porque los otros nombres son demasiado generales y pueden
1791 describir, por ejemplo, algoritmos donde no hay copia de celdas o donde no hay
1792 2 semi-espacios (como se verá en :ref:`ref_gc_art`).
1794 Al igual que el :ref:`ref_gc_mark_sweep` este algoritmo es :ref:`indirecto
1795 <ref_gc_direct>`, :ref:`no incremental <ref_gc_inc>` y :ref:`stop-the-world
1796 <ref_gc_concurrent>`. Las diferencias con los esquemas vistos hasta ahora son
1797 evidentes. La principal ventaja sobre el marcado y barrido (que requiere una
1798 pasada sobre el *live set*, el marcado, y otra sobre el *heap* entero, el
1799 barrido) es que este método require una sola pasada y sobre las celdas vivas
1800 del *heap* solamente. La principal desventaja es copia memoria, lo que puede
1801 ser particularmente costoso, además de requerir, como mínimo, el doble de
1802 memoria de lo que el *mutator* realmente necesita. Esto puede traer en
1803 particular problemas con la memoria virtual y el caché, por la pobre localidad
1806 Por lo tanto los recolectores de este tipo pueden ser convenientes por sobre
1807 el marcado y barrido cuando se espera que el *live set* sea muy pequeño luego
1808 de una recolección. En estos casos el trabajo realizado por este tipo de
1809 recolectores puede ser considerablemente menor que el del marcado y barrido.
1810 Y por el contrario, si el *working set* es pequeño, al ser *compactado* en
1811 memoria puede mejorar la localidad de referencia (si el *working set* es
1812 grande se corre el riesgo de que la localidad de referencia empeore al
1813 moverse las celdas).
1819 A continuación se presenta un sencillo ejemplo del algoritmo. Se parte de una
1820 estructura simple con 4 celdas en el *Fromspace* (que incluye un pequeño ciclo
1821 para mostrar que este algoritmo tampoco tiene inconvenientes para
1822 recolectarlos). Asumimos que ya no queda lugar en el *Fromspace* por lo que
1823 comienza la ejecución de ``collect()``. Se comienza por el *root set* que
1824 apunta a ``h3``, por lo tanto ésta es movida al *Tospace* primero, dejando una
1825 *forwarding address* a la nueva ubicación (ver figura
1826 :vref:`fig:gc-copy-ex-1`).
1828 .. fig:: fig:gc-copy-ex-1
1830 Ejemplo de recolección con copia de semi-espacios (parte 1).
1834 Estructura inicial del *heap*. El *Fromspace* está complete y se inicial
1840 +--------------------------------------------------+
1842 | /--------------------------------\ |
1843 | | /--------\ /------\ | |
1845 | ______|_V________|__V______|___________V______ |
1846 | ZZZZGGGGGGGGZZZZGGGGGGGGggggggggZZZZGGGGGGGGZZZZ |
1847 | ZZZZGGGGGGGGZZZZGGGGGGGGggggggggZZZZGGGGGGGGZZZZ |
1848 | ~~~~~~~~~|~~~~~~~~~~A~~~~~~~A~~~~~~~~~~~~~~~~~ |
1849 | h1 | h2 | h3 | h4 |
1851 | \----+"root set" |
1855 | ______________________________________________ |
1856 | BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB |
1857 | BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB |
1858 | A~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
1862 +--------------------------------------------------+
1866 Se sigue la referencia del *root set*, copiando ``h3`` al *Tospace*
1867 y dejando una *forwarding address*.
1872 +--------------------------------------------------+
1874 | /--------------------------------\ |
1875 | | /--------\ /------\ | |
1877 | ______|_V________|__V______|___________V______ |
1878 | ZZZZGGGGGGGGZZZZGGGGGGGGffffffffZZZZGGGGGGGGZZZZ |
1879 | ZZZZGGGGGGGGZZZZGGGGGGGGffffffffZZZZGGGGGGGGZZZZ |
1880 | ~~~~~~~~~|~~~~~~~~~~A~~~~~~~|A~~~~~~~~~~~~~~~~ |
1881 | h1 | h2 | h3 || h4 |
1883 | +\----+"root set" |
1885 | /-------------------------+ |
1887 | V_____________________________________________ |
1888 | HHHHHHHHBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB |
1889 | HHHHHHHHBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB |
1890 | ~~~~~~~A~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
1894 +--------------------------------------------------+
1897 A continuación se copian las *hijas* de ``h3``, en este caso sólo ``h2``, que
1898 se ubica en el *Tospace* a continuación de ``h3``, dejando nuevamente su
1899 ``forwarding address`` en la celda original. Al proceder recursivamente, se
1900 procede a copiar ``h1`` al *Tospace*, dejando una vez más la *forwarding
1901 address* en la celda original y procediendo con las hijas. Aquí podemos
1902 observar que al seguirse la referencia :math:`h1 \to h2`, como ``h2`` ya había
1903 sido visitada, solamente se actualiza la referencia apuntando a la nueva
1904 ubicación de ``h2`` pero no se vuelve a copiar la celda (ver figura
1905 :vref:`fig:gc-copy-ex-2`).
1907 .. fig:: fig:gc-copy-ex-2
1909 Ejemplo de recolección con copia de semi-espacios (parte 2).
1913 Se sigue :math:`h3 \to h2`, copiando ``h2`` al *Tospace* y dejando una
1914 *forwarding address*.
1919 +--------------------------------------------------+
1921 | /--------------------------------\ |
1922 | | /--------\ /------\ | |
1924 | ______|_V________|__V______|___________V______ |
1925 | ZZZZGGGGGGGGZZZZFFFFFFFFffffffffZZZZGGGGGGGGZZZZ |
1926 | ZZZZGGGGGGGGZZZZFFFFFFFFffffffffZZZZGGGGGGGGZZZZ |
1927 | ~~~~~~~~~|~~~~~~~~~~A|~~~~~~|A~~~~~~~~~~~~~~~~ |
1928 | h1 | h2 || h3 || h4 |
1929 | \----------/+ || |
1930 | / +\----+"root set" |
1932 | /------+------------------+ |
1934 | V______V______________________________________ |
1935 | HHHHHHHHhhhhhhhhBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB |
1936 | HHHHHHHHhhhhhhhhBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB |
1937 | ~~|~~~~~~A~~~~~A~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
1939 | \------/ \----+"free" |
1941 +--------------------------------------------------+
1945 Se sigue :math:`h2 \to h1`, copiando ``h1``. Luego :math:`h1 \to h2`
1946 pero ``h2`` no se copia, sólo se actualiza la referencia con la
1947 *forwarding address*.
1952 +--------------------------------------------------+
1954 | /--------------------------------\ |
1955 | | /--------\ /------\ | |
1957 | ______|_V________|__V______|___________V______ |
1958 | ZZZZFFFFFFFFZZZZFFFFFFFFffffffffZZZZGGGGGGGGZZZZ |
1959 | ZZZZFFFFFFFFZZZZFFFFFFFFffffffffZZZZGGGGGGGGZZZZ |
1960 | ~~~~~~~|~|~~~~~~~~~~A|~~~~~~|A~~~~~~~~~~~~~~~~ |
1961 | h1 | | h2 || h3 || h4 |
1962 | \-+----------/+ || |
1963 | +-----+ / +\-----+"root set" |
1965 | /------+-------+----------+ |
1967 | V______V_______V______________________________ |
1968 | HHHHHHHHhhhhhhhhHHHHHHHHBBBBBBBBBBBBBBBBBBBBBBBB |
1969 | HHHHHHHHhhhhhhhhHHHHHHHHBBBBBBBBBBBBBBBBBBBBBBBB |
1970 | ~~|~~~~~~A~|~A~~|~A~~~~A~~~~~~~~~~~~~~~~~~~~~~ |
1972 | \------/ | \--/ | \----+"free" |
1973 | "Tospace" \------/ |
1974 +--------------------------------------------------+
1977 Se termina de copiar recursivamente las hijas de ``h1`` al copiar ``h4`` que
1978 resulta la última celda (sin hijas). Finalmente se invierten los roles de los
1979 semi-espacios y se actualiza la referencia del *root set* para que apunte a la
1980 nueva ubicación de ``h3``, como se muestra en la figura
1981 :vref:`fig:gc-copy-ex-3`.
1983 .. fig:: fig:gc-copy-ex-3
1985 Ejemplo de recolección con copia de semi-espacios (parte 3).
1989 Se sigue :math:`h1 \to h4` copiando `h4`` al *Tospace* y dejando una
1990 *forwarding address*.
1995 +--------------------------------------------------+
1997 | /--------------------------------\ |
1998 | | /--------\ /------\ | |
2000 | ______|_V________|__V______|___________V______ |
2001 | ZZZZFFFFFFFFZZZZFFFFFFFFffffffffZZZZFFFFFFFFZZZZ |
2002 | ZZZZFFFFFFFFZZZZFFFFFFFFffffffffZZZZFFFFFFFFZZZZ |
2003 | ~~~~~~~|~|~~~~~~~~~~A|~~~~~~|A~~~~~~~~~~|~~~~~ |
2004 | h1 | | h2 || h3 || h4 \----\ |
2005 | \-+----------/+ || | |
2006 | +-----+ / +----/\---+"root set" | |
2007 | +-------+---+ / | |
2008 | /------+-------+-----+ /--------------------/ |
2009 | | h3 | h2 | h1 | h4 |
2010 | V______V_______V________V_____________________ |
2011 | HHHHHHHHhhhhhhhhHHHHHHHHhhhhhhhhBBBBBBBBBBBBBBBB |
2012 | HHHHHHHHhhhhhhhhHHHHHHHHhhhhhhhhBBBBBBBBBBBBBBBB |
2013 | ~~|~~~~~~A~|~A~~|~A~|~~~~~~A~~~A~~~~~~~~~~~~~~ |
2014 | | | | | | | | | | |
2015 | \------/ | \--/ | \------/ \----+"free" |
2016 | "Tospace" \------/ |
2017 +--------------------------------------------------+
2021 Se finaliza la recolección, se intercambian los roles de los
2022 semi-espacios y se actualiza la referencia del *root set*.
2027 +--------------------------------------------------+
2032 | ______________________________________________ |
2033 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA |
2034 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA |
2035 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
2042 | V______________________________________________ |
2043 | HHHHHHHHhhhhhhhhHHHHHHHHhhhhhhhhBBBBBBBBBBBBBBBB |
2044 | HHHHHHHHhhhhhhhhHHHHHHHHhhhhhhhhBBBBBBBBBBBBBBBB |
2045 | ~~|~~~~~~A~|~A~~|~A~|~~~~~~A~~~A~~~~~~~~~~~~~~ |
2046 | | | | | | | | | | |
2047 | \------/ | \--/ | \------/ \---+"free" |
2048 | "Fromspace" \------/ |
2049 +--------------------------------------------------+
2056 ----------------------------------------------------------------------------
2058 La manera en que la investigación sobre recolección de basura ha crecido es
2059 realmente sorprendente. Hay, al menos, 2995 publicaciones sobre recolección de
2060 basura registradas al momento de escribir este documento [GCBIB]_. Esto hace
2061 que el análisis del estado del arte sea particularmente complejo y laborioso.
2063 Analizar todas las publicaciones existentes es algo excede los objetivos de
2064 este trabajo, por lo tanto se analizó solo una porción significativa,
2065 utilizando como punto de partida a [JOLI96]_.
2067 De este análisis se observó que la gran mayoría de los algoritmos son
2068 combinaciones de diferentes características básicas; por lo tanto se intentó
2069 aislar estas características que son utilizadas como bloques de construcción
2070 para algoritmos complejos. Ésta tampoco resultó ser una tarea sencilla debido
2071 a que muchos de estos bloques de construcción básicos están interrelacionados
2072 y encontrar una división clara para obtener características realmente atómicas
2075 La construcción de recolectores más complejos se ve alimentada también por la
2076 existencia de recolectores *híbridos*; es decir, recolectores que utilizan más
2077 de un algoritmo dependiendo de alguna característica de la memoria
2078 a administrar. No es poco común observar recolectores que utilizan un
2079 algoritmo diferente para celdas que sobreviven varias recolecciones que para
2080 las que mueren rápidamente, o que usan diferentes algoritmos para objetos
2081 pequeños y grandes, o que se comporten de forma conservativa para ciertas
2082 celdas y sean precisos para otras.
2084 De todas estas combinaciones resulta el escenario tan fértil para la
2085 investigación sobre recolección de basura.
2087 A continuación se presentan las principales clases de algoritmos
2088 y características básicas encontradas durante la investigación del estado del
2089 arte. La separación de clases y aislamiento de características no es siempre
2090 trivial, ya que hay ciertas clases de recolectores que están interrelacionadas
2091 (o ciertas características pueden estar presentes sólo en recolectores de una
2092 clase en particular).
2098 Recolección directa / indirecta
2099 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2101 Generalmente se llama recolección **directa** a aquella en la cual el
2102 compilador o lenguaje instrumenta al *mutator* de forma tal que la información
2103 sobre el grafo de conectividad se mantenga activamente cada vez que hay un
2104 cambio en él. Normalmente se utiliza un contador de referencia en cada celda
2105 para este propósito, permitiendo almacenar en todo momento la cantidad de
2106 nodos que apuntan a ésta (ver :ref:`ref_gc_rc`). Esto permite reclamar una
2107 celda instantáneamente cuando el *mutator* deja de hacer referencia a ella.
2108 Este tipo de recolectores son inherentemente :ref:`incrementales
2111 Por el contrario, los recolectores **indirectos** normalmente no
2112 interfieren con el *mutator* en cada actualización del grafo de
2113 conectividad (exceptuando algunos :ref:`recolectores incrementales
2114 <ref_gc_inc>` que a veces necesitan instrumentar el *mutator* pero no para
2115 mantener el estado del grafo de conectividad completo). La recolección se
2116 dispara usualmente cuando el *mutator* requiere alocar memoria pero no hay
2117 más memoria libre conocida disponible y el recolector se encarga de generar
2118 la información de conectividad desde cero para determinar qué celdas son
2121 Esta es la madre de toda clasificación, también conocidos como :ref:`conteo de
2122 referencias <ref_gc_rc>` (directa) y *traicing garbage collection*
2123 (indirecta). Prácticamente todos los recolectores menos el :ref:`conteo de
2124 referencias <ref_gc_rc>` están dentro de esta última categoría (como por
2125 ejemplo, el :ref:`marcado y barrido <ref_gc_mark_sweep>` y :ref:`copia de
2126 semi-espacio <ref_gc_copy>`).
2128 Otros ejemplos de recolectores modernos *directos* son el recolector de
2129 basura de Python_ [NAS00]_ y [LINS05]_ (aunque ambos tiene un algoritmo
2130 *indirecto* para recuperar ciclos).
2136 Recolección incremental
2137 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2139 Recolección incremental es aquella que se realiza de forma intercalada con
2140 el *mutator*. En general el propósito es disminuir el tiempo de las pausas
2141 causadas por el recolector (aunque generalmente el resultado es un mayor
2142 costo total de recolección en términos de tiempo).
2144 De los `algoritmos clásicos`_ el único que es incremental en su forma más
2145 básica es el :ref:`conteo de referencias <ref_gc_rc>`. Otros recolectores
2146 pueden hacerse incrementales de diversas maneras, pero en general consta de
2147 hacer parte del trabajo de escanear el grafo de conectividad cada vez que el
2148 *mutator* aloca memoria. En general para hacer esto es también necesario
2149 instrumentar al *mutator* de forma tal que informe al recolector cada vez que
2150 cambia el grafo de conectividad, para que éste pueda marcar al sub-grafo
2151 afectado por el cambio como *desactualizado* y así re-escanearlo nuevamente en
2152 la próxima iteración. Para realizar esto en recolectores :ref:`indirectos
2153 <ref_gc_direct>` se utiliza la :ref:`abstracción tricolor
2154 <ref_gc_intro_tricolor>`; cuando el *mutator* cambia una referencia, se marca
2155 *gris* la celda que la contiene, de modo que el recolector vuelva a visitarla.
2157 En general la eficiencia de los recolectores incrementales disminuye
2158 considerablemente cuando el *mutator* actualiza muy seguido el grafo de
2159 conectividad, porque debe re-escanear sub-grafos que ya había escaneado una
2160 y otra vez. A esto se debe también que en general el tiempo de
2161 procesamiento total de un recolector incremental sea mayor que uno no
2162 incremental, aunque el tiempo de pausa de una recolección sea menor.
2164 Ejemplos de recolectores que se encuentran dentro de esta categoría son
2165 [BOEH91]_, [LINS05]_,
2169 .. _ref_gc_concurrent:
2171 Recolección concurrente / paralela / *stop-the-world*
2172 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2174 Los recolectores concurrentes son aquellos que pueden correr en paralelo
2175 con el *mutator*. Por el contrario, aquellos que pausan el *mutator* para
2176 realizar la recolección son usualmente denominados *stop-the-world* (*detener
2177 el mundo*), haciendo referencia a que pausan todos los hilos del *mutator*
2178 para poder escanear el grafo de conectividad de forma consistente. Hay una
2179 tercera clase de colectores que si bien son *stop-the-world*, utilizan todos
2180 los hilos disponibles para realizar la recolección (ver figura
2181 :vref:`fig:gc-concurrent`).
2183 .. fig:: fig:gc-concurrent
2185 Distintos tipos de recolectores según el comportamiento en ambientes
2195 ___________________________________________________________________
2197 | HHHHHHHHHZZZZZZZZZZZZZHHHHHHHHHHHHZZZZZZZZZZZZZHHHHHHHHHHHHHHHHHH |
2199 | HHHHHHHHHZZZZZZZZZZZZZHHHHHHHHHHHHXXXXXXXXXXXXXHHHHHHHHHHHHHHHHHH |
2201 | HHHHHHHHHXXXXXXXXXXXXXHHHHHHHHHHHHZZZZZZZZZZZZZHHHHHHHHHHHHHHHHHH |
2203 | HH Mutator ZZ Inactivo XX Recolector |
2204 |___________________________________________________________________|
2213 ___________________________________________________________________
2215 | HHHHHHHHHXXXXXHHHHHHHHHHHHHHHHHHHHXXXXXHHHHHHHHHHHHHHHHHHHHHHHHHH |
2217 | HHHHHHHHHXXXXXHHHHHHHHHHHHHHHHHHHHXXXXXHHHHHHHHHHHHHHHHHHHHHHHHHH |
2219 | HHHHHHHHHXXXXXHHHHHHHHHHHHHHHHHHHHXXXXXHHHHHHHHHHHHHHHHHHHHHHHHHH |
2221 | HH Mutator ZZ Inactivo XX Recolector |
2222 |___________________________________________________________________|
2231 ___________________________________________________________________
2233 | HHHHHHHHHZZHHHHHHHHHHHHHHHHHHHHHHHZZHHHHHHHHHHHHHHHHHHHHHHHHHHHHH |
2235 | HHHHHHHHHZZHHHHHHHHHHHHHHHHHHHHHHHZZHHHHHHHHHHHHHHHHHHHHHHHHHHHHH |
2237 | ZZZZZZZZZXXXXXXXXXXXXXXXZZZZZZZZZZXXXXXXXXXXXXXXXZZZZZZZZZZZZZZZZ |
2239 | HH Mutator ZZ Inactivo XX Recolector |
2240 |___________________________________________________________________|
2243 Para lograr que un recolector sea concurrente generalmente el mecanismo es
2244 similar al necesario para hacer un :ref:`recolector incremental
2245 <ref_gc_inc>`: hay que instrumentar al *mutator* para que informe al
2246 recolector cuando se realiza algún cambio en el grafo de conectividad, de
2247 forma tal que pueda volver a escanear el sub-grafo afectado por el cambio.
2249 Esto también trae como consecuencia el incremento en el tiempo total que
2250 consume el recolector, debido a la necesidad de re-escanear sub-grafos que
2251 han sido modificados, además de la sincronización necesaria entre *mutator*
2254 ¿Cúal es la idea entonces de un recolector concurrente? Una vez más, al igual
2255 que los recolectores incrementales, el principal objetivo es disminuir las
2256 largas pausas provocadas por la recolección de basura. Sin embargo, este tipo
2257 de algoritmos además permite hacer un mejor aprovechamiento de las
2258 arquitecturas *multi-core* [#gcmulticore]_ que cada vez se afirman más, ya que
2259 el *mutator* y el recolector pueden estar corriendo realmente en paralelo,
2260 cada uno en un procesador distinto. Algunos recolectores van más allá
2261 y permiten incluso paralelizar la recolección de basura en varios hilos
2262 ([HUEL98]_, [LINS05]_). Otros ejemplos de recolectores concurrentes (aunque no
2263 ofrece paralelización del procesamiento del recolector en varios hilos) son
2264 [BOEH91]_, [RODR97]_.
2266 .. [#gcmulticore] Una arquitectura *multi-core* es aquella que combina dos
2267 o más núcleos (*cores*) independientes que trabajan a la misma frecuencia,
2268 pero dentro de un solo circuito integrado o procesador.
2270 Todos los :ref:`algoritmos clásicos <ref_gc_classic>` que se han citado son
2271 del tipo *stop-the-world*.
2275 .. _ref_gc_free_list:
2277 Lista de libres / *pointer bump allocation*
2278 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2280 Esta clasificación se refiere principalmente a la forma en que se organiza el
2281 *heap*, íntimamente relacionado al *low level allocator*. Si bien se ha dicho
2282 que en este trabajo no se prestará particular atención a este aspecto, en
2283 ciertos algoritmos es tan relevante que tampoco es sensato pasarlo por alto
2286 En términos generales, hay dos formas fundamentales de organizar el *heap*,
2287 manteniendo una lista de libres o realizando *pointer bump allocation*, como
2288 se explicó en :ref:`ref_gc_copy`. La primera forma consiste, a grandes rasgos,
2289 en separar el *heap* en celdas (que pueden agruparse según tamaño)
2290 y enlazarlas en una lista de libres. Al solicitarse una nueva celda
2291 simplemente se la desenlaza de la lista de libres. Por otro lado, cuando el
2292 recolector detecta una celda *muerta*, la vuelve a enlazar en la lista de
2293 libres. Este es un esquema simple pero con limitaciones, entre las
2294 principales, el costo de alocar puede ser alto si hay muchos tamaños distintos
2295 de celda y soportar tamaño de celda variable puede ser complejo o acarrear
2296 muchas otras ineficiencias. :ref:`ref_gc_mark_sweep` en general usa este
2297 esquema, al igual que :ref:`ref_gc_rc`.
2299 Otro forma de organizar el *heap* es utilizándolo como una especie de *stack*
2300 en el cual para alocar simplemente se incrementa un puntero. Este esquema es
2301 simple y eficiente, si el recolector puede mover celdas (ver
2302 :ref:`ref_gc_moving`); de otra manera alocar puede ser muy costoso si hay que
2303 buscar un *hueco* en el heap (es decir, deja de reducirse a incrementar un
2304 puntero). El clásico ejemplo de esta familia es :ref:`ref_gc_copy`.
2306 Sin embargo, entre estos dos extremos, hay todo tipo de híbridos. Existen
2307 recolectores basados en *regiones*, que se encuentran en un punto intermedio.
2308 Dentro de una región se utiliza un esquema de *pointer bump allocation* pero
2309 las regiones en sí se administran como una lista de libres (como por ejemplo
2310 [BLAC08]_). Otra variación (más común) de este esquema son los *two level
2311 allocators* que alocan páginas completas (similar a las regiones) y dentro de
2312 cada página se alocan las celdas. Ambas, páginas y celdas, se administran como
2313 listas de libres (ejemplos que utilizan este esquema son [BOEHWD]_ y el
2314 :ref:`recolector actual de D <ref_dgc_actual>`).
2320 Movimiento de celdas
2321 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2323 Otra característica muy importante del recolector de basura es si mueve las
2324 celdas o no. En general el movimiento de celdas viene de la mano del esquema
2325 de :ref:`pointer bump allocation <ref_gc_free_list>`, ya que *compacta* todas
2326 las celdas *vivas* al comienzo del *heap* luego de una recolección,
2327 permitiendo este esquema para alocar nuevas celdas, pero puede utilizarse en
2328 esquemas híbridos como recolectores basados en *regiones* (por ejemplo
2331 Además los recolectores con movimiento de celdas deben ser :ref:`precisos
2332 <ref_gc_conserv>`, porque es necesario tener la información completa de los
2333 tipos para saber cuando actualizar los punteros (de otra manera se podría
2334 escribir un dato de una celda que no era un puntero). Para que un recolector
2335 pueda mover celdas, aunque sea parcialmente, en recolectores *semi-precisos*
2336 se utiliza un método conocido como *pinning* (que significa algo como *pinchar
2337 con un alfiler*); una celda es *pinned* (*pinchada*) cuando hay alguna
2338 referencia no-precisa a ella, y por lo tanto no puede ser movida (porque no se
2339 puede estar seguro si es posible actualizar dicha referencia).
2341 La ventaja principal de los colectores con movimiento es la posibilidad de
2342 utilizar :ref:`pointer bump allocation <ref_gc_free_list>` y que es sencillo
2343 implementar recolectores :ref:`generacionales <ref_gc_part>` sobre estos.
2345 De los algoritmos clásicos sólo la :ref:`copia de semi-espacios <ref_gc_copy>`
2346 mueve celdas, el :ref:`conteo de referencias <ref_gc_rc>` y :ref:`marcado
2347 y barrido <ref_gc_mark_sweep>` no lo hacen. Además hay otro algoritmo bastante
2348 básico que mueve celdas, el **marcado y compactado**. Éste no tiene
2349 2 semi-espacios, directamente mueve las celdas compactándolas al comienzo del
2350 *heap*. El algoritmo es un poco más complejo que la :ref:`copia de
2351 semi-espacios <ref_gc_copy>` pero suele poder proveer una mayor localidad de
2352 referencia y *desperdicia* un semi-espacio que está inutilizado salgo en el
2353 momento de la recolección. Por ejemplo para Mono_, que antes usaba un
2354 recolector conservativo sin movimiento ([BOEHWD]_) se está implementando un
2355 recolector de este tipo [MOLAWE]_ [MOLA06]_.
2361 Recolectores conservativos vs precisos
2362 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2364 Los recolectores *conservativos* son aquellos que tienen la capacidad de poder
2365 lidiar con un *root set* o celdas que no tengan información de tipos asociada.
2366 Esto significa que el recolector no sabe donde hay punteros (o referencias) en
2367 una celda o raiz dada. Es decir, una ubicación particular puede ser un puntero
2368 o no. Esto trae una variada cantidad de problemas, como retención de celdas
2369 que en realidad son *basura* simplemente porque hay algún dato que coincide
2370 con la dirección de memoria en la que está almacenada esa celda *basura*
2371 [#gcflasepos]_. Además los recolectores puramente conservativos no puede mover
2372 celdas (ver :ref:`ref_gc_moving`), porque no pueden arriesgarse a actualizar
2373 los punteros por el riesgo que existe de que sean falsos positivos.
2375 .. [#gcflasepos] Esto es lo que se conoce como un *falso positivo*, algo que
2376 aparenta ser un puntero pero en realidad no lo es.
2378 Sin embargo hay ciertas circunstancias que hacen que no quede más remedio que
2379 el recolector sea conservativo, por ejemplo cuando se utiliza un recolector de
2380 basura para un lenguaje que no ha sido pensado para tener uno (como C o C++).
2382 Por el contrario, los recolectores que poseen a su disposición información
2383 completa sobre el tipo de la celda, y por ende información sobre cuales de sus
2384 campos son realmente punteros, son denominados *precisos*. Estos recolectores
2385 no están sujetos a estas limitaciones y por lo tanto son potencialmente más
2386 eficientes en cuanto a tiempo y espacio. Los lenguajes que fueron diseñados
2387 para tener un recolector de basura (y en especial aquellos que son de relativo
2388 alto nivel) en general disponen de recolectores precisos.
2390 Hay casos donde se posee información de tipos para algunas celdas solamente,
2391 o más comunmente se posee información de tipos de celdas que se encuentran en
2392 el *heap* pero no para el *stack* y registros (por ejemplo [MOLA06]_). En
2393 estos casos se puede adoptar un esquema híbrido y tratar algunas referencias
2394 de forma conservativa y otras de forma precisa, de manera de mitigar, aunque
2395 sea de forma parcial, los efectos adversos de los recolectores conservativos.
2396 Estos recolectores son conocidos como *semi-precisos*. Los recolectores
2397 semi-precisos pueden mover celdas si utilizan un mecanismo de *pinning* (ver
2398 :ref:`ref_gc_moving`).
2400 El ejemplo de recolector conservativo por excelencia es el recolector
2401 `Boehm-Demers-Wiser`_ ([BOEH88]_, [BOEH91]_, [BOEH93]_, [BOEHWD]_) aunque
2402 puede comportarse de forma semi-precisa si el usuario se encarga de darle la
2403 información de tipos (en cuyo caso el recolector deja de ser transparente para
2404 el usuario). Otros ejemplos de recolectores con cierto grado de
2405 conservativismo son el :ref:`recolector actual de D <ref_dgc_actual>`
2412 Recolección particionada
2413 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2415 Otra forma de reducir la cantidad de pausas y la cantidad de trabajo realizado
2416 por el recolector en general es particionando el *heap* de manera tal de
2417 recolectar solo las partes donde más probabilidad de encontrar *basura* haya.
2419 Entonces, si el recolector tiene algún mecanismo para identificar zonas de
2420 alta concentración de *basura* puede hacer la recolección solo en ese área
2421 donde el trabajo va a ser mejor recompensado (ver :vref:`fig:gc-part`).
2423 .. fig:: fig:gc-part
2425 Concentración de basura en distintas particiones del *heap*.
2430 _______________________________________________________________________
2432 | +-----------------------------+ +-----------------------------+ |
2433 | / Baja \ / Alta \ |
2435 | GGGGGGGZZGGGGGZZGGGGGGGGZZGGGGGGGGZZZZZGGZZZZZZZZZZZZZZZZGGZZZZZZGGZZ |
2436 | GGGGGGGZZGGGGGZZGGGGGGGGZZGGGGGGGGZZZZZGGZZZZZZZZZZZZZZZZGGZZZZZZGGZZ |
2438 | GG Celdas vivas ZZ Basura |
2439 |_______________________________________________________________________|
2442 Sin embargo encontrar zonas de alta concentración no es trivial. La forma más
2443 divulgada de encontrar estas zonas es particionando el *heap* en un área
2444 utilizada para almacenar celdas *jóvenes* y otra para celdas *viejas*. Una
2445 celda *vieja* es aquella que ha *sobrevivido* una cantidad N de recolecciones,
2446 mientras que el resto se consideran *jóvenes* (las celdas *nacen* jóvenes).
2447 Los recolectores que utilizan este tipo de partición son ampliamente conocido
2448 como recolectores **generacionales**. La *hipótesis generacional* dice que el
2449 área de celdas jóvenes tiene una mayor probabilidad de ser un área de alta
2450 concentración de basura [JOLI96]_. Basandose en esto, los recolectores
2451 generacionales primero intentan recuperar espacio del área de celdas jóvenes
2452 y luego, de ser necesario, del área de celdas viejas. Es posible tener varias
2453 generaciones e ir subiendo de generación a generación a medida que es
2454 necesario. Sin embargo en general no se obtienen buenos resultados una vez que
2455 se superan las 3 particiones. La complejidad que trae este método es que para
2456 recolectar la generación joven es necesario tomar las referencias de la
2457 generación vieja a la joven como parte del *root set* (de otra forma podrían
2458 tomarse celdas como *basura* que todavía son utilizadas por las celdas
2459 viejas). Revisar toda la generación vieja no es una opción porque sería
2460 prácticamente lo mismo que realizar una recolección del *heap* completo. La
2461 solución está entonces, una vez más, en instrumentar el *mutator* para que
2462 avise al recolector cuando cambia una referencia de la generación vieja a la
2463 joven (no es necesario monitorear las referencias en sentido inverso ya que
2464 cuando se recolecta la generación vieja se hace una recolección del *heap*
2467 Sin embargo, a pesar de ser este el esquema más difundido para particionar el
2468 *heap* y realizar una recolección parcial sobre un área de alta concentración
2469 de basura no es la única. Otros recolectores proponen hacer un análisis
2470 estático del código revisando la conectividad entre los objetos según sus
2471 tipos (esto es posible solo en lenguajes con tipado estático), de manera tal
2472 de separar en distintas áreas grupos de tipos que no pueden tener referencias
2473 entre sí [HIRZ03]_. Este análisis hace que sea inecesario instrumentar el
2474 *mutator* para reportar al recolector cambios de referencias
2475 inter-particiones, sencillamente porque queda demostrado que no existe dicho
2476 tipo de referencias. Esto quita una de las principale ineficiencias
2477 y complejidades del esquema generacional.
2481 .. include:: links.rst
2483 .. vim: set ts=3 sts=3 sw=3 et tw=78 :