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 del
21 *heap* [#gcheap]_ una vez que el programa ha dejado de hacer referencia a ella
22 (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 no
28 la necesita. A diferencia del *stack*, la duración de la *reserva* no está
29 atada a un bloque de código.
31 A medida que el tiempo pasa, cada vez los programas son más complejos y es más
32 compleja la administración de memoria. Uno de los aspectos más importantes de
33 un recolector de basura es lograr un mayor nivel de abstracción y modularidad,
34 dos conceptos claves en la ingeniería de software [JOLI96]_. En particular, al
35 diseñar o programar bibliotecas, de no haber un recolector de basura, **la
36 administración de memoria pasa a ser parte de la interfaz**, lo que produce
37 que los módulos tengan un mayor grado de acoplamiento.
39 Además hay una incontable cantidad de problemas asociados al manejo explícito
40 de memoria que simplemente dejan de existir al utilizar un recolector de
41 basura. Por ejemplo, los errores en el manejo de memoria (como *buffer
42 overflows* [#gcbuff]_ o *dangling pointers* [#gcdang]_) son la causa más
43 frecuente de problemas de seguridad [BEZO06]_.
45 .. [#gcbuff] Un *buffer overflow* (*desbordamiento de memoria* en
46 castellano) se produce cuando se copia un dato a un área de memoria que no
47 es lo suficientemente grande para contenerlo. Esto puede producir que el
48 programa sea abortado por una violación de segmento, o peor, sobreescribir
49 un área de memoria válida, en cuyo caso los resultados son impredecibles.
51 .. [#gcdang] Un *dangling pointer* (*puntero colgante* en castellano) es un
52 puntero que apunta a un área de memoria inválida. Ya sea porque el elemento
53 apuntado no es el mismo tipo o porque la memoria ya ha sido liberada. Al
54 ser desreferenciado, los resultados son impredecibles, el programa podría
55 abortarse por una violación de segmento o podrían pasar peores cosas si el
56 área de memoria fue re-asignada para almacenar otro objeto.
58 La recolección de basura nació junto a Lisp_ a finales de 1950 y en los
59 siguientes años estuvo asociada principalmente a lenguajes funcionales, pero
60 en la actualidad está presente en prácticamente todos los lenguajes de
61 programación, de alto o bajo nivel, aunque sea de forma opcional. En los
62 últimos 10 años tuvo un gran avance, por la adopción en lenguajes de
63 desarrollo rápido utilizados mucho en el sector empresarial, en especial
64 Java_, que fue una plataforma de facto para la investigación y desarrollo de
65 recolectores de basura (aunque no se limitaron a este lenguaje las
68 En las primeras implementaciones de recolectores de basura la penalización en
69 la eficiencia del programa se volvía prohibitiva para muchas aplicaciones. Es
70 por esto que hubo bastante resistencia a la utilización de recolectores de
71 basura, pero el avance en la investigación fue haciendo que cada vez sea una
72 alternativa más viable al manejo manual de memoria, incluso para apliaciones
73 con altos requerimientos de eficiencia. En la actualidad un programa que
74 utiliza un recolector moderno puede ser comparable en eficiencia con uno que
75 utiliza un esquema manual. En particular, si el programa fue diseñado con el
76 recolector de basura en mente en ciertas circunstancias puede ser incluso más
77 eficiente que uno que hace manejo explícito de la memoria. Muchos recolectores
78 mejoran la localidad de referencia [#gcreflocal]_, haciendo que el programa
79 tenga un mejor comportamiento con el caché y la memoria virtual.
81 .. [#gcreflocal] Localidad de referencia es la medida en que los accesos
82 sucesivos de memoria cercana espacialmente son cercanos también en el
83 tiempo. Por ejemplo, un programa que lee todos los elementos de una matriz
84 contigua de una vez o que utiliza la misma variable repetidamente tiene
85 buena localidad referencia. Una buena localidad de referencia interactúa
86 bien con la memoria virtual y caché, ya que reduce el conjunto de trabajo
87 (o *working set*) y mejora la probabildad de éxito (*hit rate*).
89 El recolector de basura debe tener un comportamiento correcto y predecible
90 para que sea útil, si el programador no puede confiar en el recolector de
91 basura, éste se vuelve más un problema que una solución, porque introduce
92 nuevos puntos de falla en los programas, y lo que es peor, puntos de falla no
93 controlados por el programador, volviendo mucho más difícil la búsqueda de
100 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
102 Los programas pueden hacer uso principalmente de 4 áreas de memoria:
105 Se trata de la memoria más básica de una computadora. Es el área de memoria
106 en la que puede operar realmente el procesador, es extremadamente escasa
107 y generalmente su uso es administrado por el lenguaje de programación (o
108 compilador más específicamente). Excepto en situaciones muy particulares,
109 realizando tareas de muy bajo nivel, un programador nunca manipula los
110 registros explícitamente.
112 Área de memoria estática:
113 Es la forma de memoria más simple que un programador utiliza
114 explícitamente. En general las variables globales se almacenan en este
115 área, que es parte inherente del programa y está disponible durante toda su
116 ejecución, por lo tanto nunca cambia su capacidad en tiempo de ejecución.
117 Es la forma más básica de administrar memoria, pero tiene una limitación
118 fundamental: **el tamaño de la memoria tiene que ser conocido en tiempo de
119 compilación**. Los primeros lenguajes de programación solo contaban con
120 este tipo de memoria (además de los registros del procesador).
123 Los primeros lenguajes de programación que hicieron uso de una pila
124 aparecieron en el año 1958 (Algol-58 y Atlas Autocode) y fueron los
125 primeros en introducir estructura de bloques, almacenando las variables
126 locales a estos bloques utilizando una pila [JOLI96]_. Esto permite
127 utilizar recursividad y tener un esquema simple de memoria dinámica. Sin
128 embargo este esquema es muy limitado porque el orden de reserva
129 y liberación de memoria tiene que estar bien establecido. Una celda
130 [#gccelda]_ asignada antes que otra nunca puede ser liberada antes que
133 .. [#gccelda] En general en la literatura se nombra a una porción de
134 memoria asignada individualmente *celda*, *nodo* u *objeto*
135 indistintamente. En este trabajo se utilizará la misma nomenclatura
136 (haciendo mención explícita cuando alguno de estos términos se refiera
137 a otra cosa, como al nodo de una lista o a un objeto en el sentido de
138 programación orientada a objetos).
141 A diferencia del *stack*, el *heap* provee un área de memoria que puede ser
142 obtenida dinámicamente pero sin limitaciones de orden. Es el tipo de
143 memoria más flexible y por lo tanto el más complejo de administrar; razón
144 por la cual existen los recolectores de basura.
146 La recolección de basura impone algunas restricciones sobre la manera de
147 utilizar el *heap*. Debido a que un recolector de basura debe ser capaz de
148 determinar el grafo de conectividad de la memoria en uso, es necesario que el
149 programa siempre tenga alguna referencia a las celdas activas en los
150 registros, memoria estática o *stack* (normalmente denominado *root set*).
152 Esto implica que una celda sea considerada basura si y sólo si no puede ser
153 alcanzada a través del grafo de conectividad que se comienza a recorrer desde
154 el *root set*. Por lo tanto, una celda está *viva* si y sólo si su dirección
155 de memoria está almacenada en una celda *raíz* (parte del *root set*) o si
156 está almacenada en otra celda *viva* del *heap*.
158 Expresado más formalmente, dada la relación :math:`M \to N`, donde :math:`M`
159 es una celda del *heap* o parte del *root set* y :math:`N` es una celda del
160 *heap*, definida como:
164 M \to N \Longleftrightarrow M \text{ almacena un puntero a } N
166 El conjunto de celdas vivas (o *live set*) queda determinado por:
170 vivas = \left\lbrace N \in Celdas \big/
171 ( \exists r \in Raices / r \to N ) \vee (\exists M \in vivas / M \to N )
174 Cabe aclarar que esta es una definición conceptual, asumiendo que el programa
175 siempre limpia una dirección de memoria almacenada en el *root set* o una
176 celda del *heap* cuando la celda a la que apunta no va a ser utilizada
177 nuevamente. Esto no es siempre cierto y los falsos positivos que esto produce
178 se conoce como un tipo de pérdida de memoria (que es posible incluso al
179 utilizar un recolector de basura) llamada pérdida de memoria *lógica*. Esto
180 puede no ser evitable (incluso cuando el programador no cometa errores) en
181 lenguajes de programación que requieran un recolector de basura conservativo.
183 Por último, siendo que el recolector de basura es parte del programa de forma
184 indirecta, es común ver en la literatura que se direfencia entre
185 2 partes del programa, el recolector de basura y el programa en sí. Dado que
186 para el recolector de basura, lo único que interesa conocer del programa en
187 sí son los cambios al grafo de conectividad de las celdas, normalmente se lo
188 llama *mutator* (mutador).
194 Recorrido del grafo de conectividad
195 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
197 El problema de encontrar las celdas *vivas* de un programa se reduce
198 a recorrer un grafo dirigido. El grafo se define como:
204 Donde :math:`V` es el conjunto de vértices, dado por las celdas de memoria
205 y :math:`A` es un conjunto de pares ordenados (aristas), dado por la relación
206 :math:`M \rightarrow N` (es decir, los punteros).
208 El grafo comienza a recorrerse desde el *root set* y todos los vértices que
209 fueron visitados componen el *live set*; el resto de los vértices son
212 Más formalmente, Definimos:
215 secuencia de vértices tal que cada uno de los vértices tiene una arista al
216 próximo vértice en la secuencia. Todo camino finito tiene un *vértice
217 inicial* y un *vértice final* (llamados en conjunto *vértices terminales*).
218 Cualquier vértice no terminal es denominado *vértice interior*.
222 \underset{v_1 \rightarrow v_N}{C} = \left\lbrace
223 v_1, \dotsc, v_N \in V \big/ \underset{i \in [1,N-1]}{\forall v_i}
224 \exists (v_i \to v_{i+1}) \in A
228 decimos que :math:`M` está *conectado* a :math:`N` si y sólo si existe un
229 camino de :math:`M` a :math:`N`.
233 M \mapsto N \Longleftrightarrow \exists \underset{M \to N}{C} \in G
236 el conjunto de celdas *vivas* está dado por todos los vértices (:math:`v`)
237 del grafo para los cuales existe una raíz en el *root set* que esté
242 Live \thickspace set = \left\lbrace v \in V \big/
243 \left( \exists r \in Root \thickspace set \big/ r \mapsto v \right)
247 la basura, o celdas *muertas*, quedan determinadas entonces por todas las
248 celdas del *heap* que no son parte del *live set*.
252 Basura = V - Live \thickspace set
254 Esto es, efectivamente, una partición del *heap* (ver figura
255 :vref:`fig:gc-heap-parts`).
258 .. fig:: fig:gc-heap-parts
260 Distintas partes de la memoria *heap*.
262 Distintas partes de la memoria, incluyendo relación entre *basura*, *live
263 set*, *heap* y *root set*.
270 node [ shape = record, width = 0, height = 0 ];
272 subgraph cluster_heap {
278 subgraph cluster_live {
291 subgraph cluster_garbage {
296 node [ style = filled, fillcolor = white ];
301 subgraph cluster_root {
306 node [ style = filled, fillcolor = gray96 ];
310 r0 -> h1 -> h2 -> h5;
311 r1 -> h5 -> h6 -> h1;
318 Al proceso de visitar los vértices *conectados* desde el *root set* se lo
319 denomina *marcado*, *fase de marcado* o *mark phase* en inglés, debido a que
320 es necesario marcar los vértices para evitar visitar 2 veces el mismo nodo en
321 casos de que el grafo contenga ciclos [#gccycle]_. De forma similar a la
322 búsqueda, que puede realizarse *primero a lo ancho* (*breadth-first*)
323 o *primero a lo alto* (*depth-first*) del grafo, el marcado de un grafo
324 también puede realizarse de ambas maneras. Cada una podrá o no tener efectos
325 en la eficiencia, en particular dependiendo de la aplicación puede convenir
326 uno u otro método para lograr una mejor localidad de referencia.
328 .. [#gccycle] Un ciclo es un camino donde el *vértice inicial* es el mismo
329 que el *vértice final*. Por lo tanto, los *vértices terminales* son
330 completamente arbitrarios, ya que cualquier *vértice interior* puede ser un
333 Un algoritmo simple (recursivo) de marcado *primero a lo alto* puede ser el
334 siguiente (asumiendo que partimos con todos los vértices sin marcar)
340 for (src, dst) in v.edges
343 function mark_phase() is
344 foreach r in root_set
347 .. [#gcpseudo] Para presentar los algoritmos se utiliza una forma simple de
348 pseudo-código. El pseudo-código se escribe en inglés para que pueda ser más
349 fácilmente contrastado con la literatura, que está en inglés. Para
350 diferenciar posiciones de memoria y punteros de las celdas en sí, se usa la
351 misma sintaxis que C, ``r*`` denota una referencia o puntero y ``*r``
352 denota "objeto al que apunta ``r``\ ". Se sobreentiende que ``r = o``
353 siempre toma la dirección de memoria de ``o``.
355 Una vez concluido el marcado, sabemos que todos los vértices con la marca son
356 parte del *live set* y que todos los vértices no marcados son *basura*. Esto
357 es conocido también como **abstracción bicolor**, dado que en la literatura se
358 habla muchas veces de *colorear* las celdas. En general, una celda sin marcar
359 es de color blanco y una marcada de color negro.
361 Puede observarse un ejemplo del algoritmo en la figura :vref:`fig:gc-mark-1`,
362 en la cual se marca el sub-grafo apuntando por ``r0``. Luego se marca el
363 sub-grafo al que apunta ``r1`` (ver figura :vref:`fig:gc-mark-2`), concluyendo
364 con el marcado del grafo completo, dejando sin marcar solamente las celdas
365 *basura* (en blanco).
368 .. fig:: fig:gc-mark-1
370 Ejemplo de marcado del grafo de conectividad (parte 1).
374 Se comienza a marcar el grafo por la raíz r0.
381 node [ shape = record, width = 0, height = 0];
382 edge [ color = gray40 ];
384 subgraph cluster_all {
387 label = "root\nset|<r0> r0\n*|<r1> r1",
393 node [ style = filled, fillcolor = gray25, fontcolor = white ];
397 root:r0 -> h1 [ style = bold, color = black ];
398 h1 -> h2 -> h5 -> h1;
407 Luego de marcar el nodo ``h1``, se procede al ``h2``.
414 node [ shape = record, width = 0, height = 0 ];
415 edge [ color = gray40 ];
417 subgraph cluster_all {
420 label = "root\nset|<r0> r0\n*|<r1> r1",
426 node [ style = filled, fillcolor = gray25, fontcolor = white ];
430 root:r0 -> h1 [ color = gray10 ];
431 h1 -> h2 [ style = bold, color = black ];
441 Luego sigue el nodo h5.
448 node [ shape = record, width = 0, height = 0 ];
449 edge [ color = gray40 ];
451 subgraph cluster_all {
454 label = "root\nset|<r0> r0\n*|<r1> r1",
460 node [ style = filled, fillcolor = gray25, fontcolor = white ];
464 root:r0 -> h1 [ color = gray10 ];
465 h1 -> h2 [ color = gray10 ];
466 h2 -> h5 [ style = bold, color = black ];
475 .. fig:: fig:gc-mark-2
477 Ejemplo de marcado del grafo de conectividad (parte 2).
481 El nodo h5 tiene una arista al h1, pero el h1 ya fue visitado, por lo
482 tanto no se visita nuevamente.
489 node [ shape = record, width = 0, height = 0 ];
490 edge [ color = gray40 ];
492 subgraph cluster_all {
495 label = "root\nset|<r0> r0\n*|<r1> r1",
501 node [ style = filled, fillcolor = gray25, fontcolor = white ];
505 root:r0 -> h1 [ color = gray10 ];
506 h1 -> h2 [ color = gray10 ];
507 h2 -> h5 [ color = gray10 ];
508 h5 -> h1 [ style = bold, color = black ];
517 Se concluye el marcado del sub-grafo al que conecta r0, se procede
518 a marcar el sub-grafo al que conecta r1, marcando al nodo h6.
525 node [ shape = record, width = 0, height = 0 ];
526 edge [ color = gray40 ];
528 subgraph cluster_all {
531 label = "root\nset|<r0> r0|<r1> r1\n*",
537 node [ style = filled, fillcolor = gray25, fontcolor = white ];
541 root:r0 -> h1 [ color = gray10 ];
542 h1 -> h2 [ color = gray10 ];
543 h2 -> h5 [ color = gray10 ];
544 h5 -> h1 [ color = gray10 ];
545 root:r1 -> h6 [ style = bold, color = black ];
554 El nodo h6 tiene una arista al h2, pero éste ya fue marcado por lo
555 que no se vuelve a visitar. No hay más raíces, se finaliza el marcado
563 node [ shape = record, width = 0, height = 0 ];
564 edge [ color = gray40 ];
566 subgraph cluster_all {
569 label = "root\nset|<r0> r0|<r1> r1\n*",
575 node [ style = filled, fillcolor = gray25, fontcolor = white ];
579 root:r0 -> h1 [ color = gray10 ];
580 h1 -> h2 [ color = gray10 ];
581 h2 -> h5 [ color = gray10 ];
582 h5 -> h1 [ color = gray10 ];
583 root:r1 -> h6 [ color = gray10 ];
584 h6 -> h2 [ style = bold, color = black ];
592 .. _gc_intro_tricolor:
595 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
597 Muchos algoritmos utilizan tres colores para realizar el marcado. El tercer
598 color, gris generalmente, indica que una celda debe ser visitada. Esto permite
599 algoritmos :ref:`concurrentes <gc_concurrent>` e :ref:`incrementales
600 <gc_inc>`, además de otro tipo de optimizaciones. Entonces, lo que plantea
601 esta abtracción es una nueva partición del heap al momento de marcar, esta vez
602 son 3 porciones: blanca, gris y negra.
604 Al principio todas las celdas se pintan de blanco, excepto el *root set* que
605 se punta de gris. Luego se van obteniendo celdas del conjunto de las grises
606 y se las pinta de negro, pintando sus hijas directas de gris.
608 Una vez que no hay más celdas grises, tenemos la garantía de que las celdas
609 negras serán el *live set* y las celdas blancas *basura*. Esto se debe a que
610 siempre se mantiene esta invariante: **ninguna celda negra apunta directamente
611 a una celda blanca**. Las celdas blancas siempre son apuntadas por celdas
612 blancas o grises. Entonces, siempre que el conjunto de celdas grises sea
613 vacío, no habrán celdas negras conectadas a blancas, siendo las celdas blancas
616 El algoritmo básico para marcar con tres colores es el siguiente (asumiendo
617 que todas las celdas parten pintadas de blanco, es decir, el conjunto blanco
618 contiene todas las celdas de memoria y los conjuntos negro y gris están
621 function mark_phase() is
622 foreach r in root_set
624 while not gray_set.empty()
627 for (src, dst) in v.edges
632 Es simple notar que este algoritmo es naturalmente no recursivo, lo que de por
633 sí ya presenta una ventaja sobre el marcado *bicolor*.
637 .. _gc_intro_services:
640 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
642 En general todos los algoritmos de recolección de basura utilizan servicios de
643 una capa inferior [#gclowlayer]_ y proveen servicios a una capa superior
646 .. [#gclowlayer] En general estos servicios están provistos directamente
647 por el sistema operativo pero también pueden estar dados por un
648 administrador de memoria de bajo nivel (o *low level allocator* en inglés).
650 .. [#gchilayer] En general estos servicios son utilizados directamente por
651 el lenguaje de programación, pero pueden ser utilizados directamente por el
652 usuario del lenguaje si éste interatúa con el recolector, ya sea por algún
653 requerimiento particular o porque el lenguaje no tiene soporte diercto de
654 recolección de basura y el recolector está implementado como una biblioteca
657 A continuación se presentan las primitivas en común que utilizan todos los
658 recolectores a lo largo de este documento.
660 Servicios utilizados por el recolector son los siguientes:
662 :math:`alloc() \to cell`:
663 obtiene una nueva celda de memoria. El mecanismo por el cual se obtiene la
664 celda es indistinto para esta sección, puede ser de una lista libre, puede
665 ser de un administrador de memoria de más bajo nivel provisto por el
666 sistema operativo o la biblioteca estándar de C (``malloc()``), etc. Cómo
667 organizar la memoria es un área de investigación completa y si bien está
668 estrechamente relacionada con la recolección de basura, en este trabajo no
669 se prestará particular atención a este aspecto (salvo casos donde el
670 recolector impone una cierta organización de memoria en el *low level
671 allocator*). Por simplicidad también asumiremos (a menos que se indique lo
672 contrario) que las celdas son de tamaño fijo. Esta restricción normalmente
673 puede ser fácilmente relajada (en los recolectores que la tienen).
676 libera una celda que ya no va a ser utilizada. La celda liberada debe haber
677 sido obtenida mediante ``alloc()``.
679 Y los servicios básicos proporcionados por el recolector son los siguientes:
681 :math:`new() \to cell`:
682 obtiene una celda de memoria para ser utilizada por el programa.
684 :math:`update(ref, cell)`:
685 notifica al recolector que la referencia :math:`ref` ahora apunta
686 a :math:`cell`. Visto más formalmente, sería análogo a decir que hubo un
687 cambio en la conectividad del grafo: la arista :math:`src \to old` cambia
688 por :math:`src \to new` (donde :math:`src` es la celda que contiene la
689 referencia :math:`ref`, :math:`old` es la celda a la que apunta la
690 referencia :math:`ref` y :math:`new` es el argumento :math:`cell`). Si
691 :math:`cell` es ``null``, sería análogo a informar que se elimina la arista
695 este servicio, según el algoritmo, puede ser utilizado para informar un
696 cambio en la conectividad del grafo, la eliminación de una arista (análogo
697 a :math:`update(ref, null)` pero sin proporcionar información sobre la
698 arista a eliminar). Esto es generalmente útil solo en :ref:`conteo de
699 referencias <gc_rc>`. Para otros recolectores puede significar que el
700 usuario asegura que no hay más referencias a esta celda, es decir, análogo
701 a eliminar el conjunto de aristas :math:`\big\lbrace (v, w) \in A , v \in
702 Live \thickspace set , w \in Live \thickspace set \big/ w = cell`.
705 indica al recolector que debe hacer un análisis del grafo de conectividad
706 en busca de *basura*. Generalmente este servicio es invocado por el propio
707 recolector cuando no hay más celdas reciclables.
709 No todos los servicios son implementados por todos los recolectores, pero son
710 lo suficientemente comunes como para describirlos de forma general en esta
711 sección. Algunos son principalmente ideados para uso interno del recolector,
712 aunque en ciertas circunstancias pueden ser utilizados por el usuario también.
719 ----------------------------------------------------------------------------
721 En la literatura se encuentran normalmente referencias a 3 algoritmos
722 clásicos, que son utilizados generalmente como bloques básicos para construir
723 recolectores más complejos. Se presentan las versiones históricas más simples
724 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 por
734 `John McCarthy`_ para Lisp_ a finales de 1950. Se trata de un método
735 :ref:`directo <gc_direct>` e :ref:`incremental <gc_inc>` por naturaleza, ya
736 que distribuye la carga de la recolección de basura durante toda la ejecución
737 del programa, cada vez que el *mutator* cambia la conectividad de algún nodo
738 del grafo de conectividad.
740 El método consiste en tener un contador asociado a cada celda que contenga la
741 cantidad de celdas **vivas** que apuntan a ésta. Es decir, es la cardinalidad
742 del conjunto de aristas que tienen por destino a la celda. Formalmente
743 podemos definir el contador :math:`rc(v)` (de *reference counter* en inglés)
744 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 de
757 conectividad cambia, es decir, cada vez que se agrega, modifica o elimina una
758 arista del grafo (o visto de una forma más cercana al código, cada vez que se
759 agrega, modifica o elimina un puntero).
761 Esta invariante es fundamental para el conteo de referencias, porque se asume
762 que si el contador es 0 entonces el *mutator* no tiene ninguna referencia a la
763 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 antiguamente
771 e incrementar en 1 el contador de la celda a la que apunta luego de la
772 modificación. Esto asegura que la invariante se mantenga durante toda la
773 ejecución del programa. Si al momento de decrementar un contador éste queda en
774 0, la celda asociada debe liberarse de forma de poder ser reciclada. Esto
775 implica que si esta celda almacena punteros, los contadores de las celdas
776 apuntadas deben ser decrementados también, porque solo deben almacenarse en el
777 contador las aristas del *live set* para mantener la invariante. De esto puede
778 resultar que otros contadores de referencia queden en 0 y más celdas sean
779 liberadas. Por lo tanto, teóricamente la complejidad de eliminar una
780 referencia puede ser :math:`O(\lvert Live \thickspace set \rvert)` en el peor
783 Las primitivas implementadas para este tipo de recolector son las siguientes
784 (acompañadas de una implementación básica)::
793 function del(cell) is
794 cell.rc = cell.rc - 1
796 foreach child* in cell.children
800 function update(ref*, cell) is
801 cell.rc = cell.rc + 1
812 El conteo de referencias tiene, sin embargo, un problema fundamental: **falla
813 con estructuras cíclicas**. Esto significa que siempre que haya un ciclo en el
814 grafo de conectividad, hay una pérdida de memoria potencial en el programa. Un
815 ciclo es un camino :math:`\underset{v \to v}{C}`, es decir, el *vértice
816 inicial* es el mismo que el *vértice final*.
818 Cuando esto sucede, las celdas que participan del ciclo tienen siempre su
819 contador mayor que 0, sin embargo puede no haber ningún elemento del *root
820 set* que apunte a una celda dentro del ciclo, por lo tanto el ciclo es
821 *basura* (al igual que cualquier otra celda que sea referenciada por el ciclo
822 pero que no tenga otras referencias externas) y sin embargo los contadores no
823 son 0. Los ciclos, por lo tanto, *rompen* la invariante del conteo de
826 Hay formas de solucionar esto, pero siempre recaen en un esquema que va por
827 fuera del conteo de referencias puro. En general los métodos para solucionar
828 esto son variados y van desde realizar un marcado del subgrafo para detectar
829 nodos hasta tener otro recolector completo de *emergencia*, pasando por tratar
830 los ciclos como un todo contar las referencias al ciclo completo en vez de
831 a cada celda en particular.
833 Incluso con este problema, el conteo de referencia sin ningún tipo de solución
834 en cuanto a la detección y recolección de ciclos fue utilizado en muchos
835 lenguajes de programación sin que su necesidad sea tan evidente. Por ejemplo
836 Python_ agregó recolección de ciclos en la versión 2.0 [NAS00]_ (liberada en
837 octubre de 2000) y PHP_ recién agrega detección de ciclos en la versión 5.3
838 (todavía no liberada al momento de escribir este documento) [PHP081]_.
846 A continuación se presenta un ejemplo gráfico para facilitar la comprensión
847 del algoritmo. Por simplicidad se asumen celdas de tamaño fijo con dos
848 punteros, ``left`` (``l``) y ``right`` (``r``) y se muestra el contador de
849 referencias abajo del nombre de cada celda. Se parte con una pequeña
850 estructura ya construída y se muestra como opera el algoritmo al eliminar
851 o cambiar una referencia (cambios en la conectividad del grafo). En un
852 comienzo todas las celdas son accesibles desde el *root set* por lo tanto son
853 todas parte del *live set*.
855 Se comienza por eliminar la referencia de ``r0`` a ``h1``, que determina que
856 ``h1`` se convirtió en *basura* (ver figura :vref:`fig:gc-rc-rm-1`). Esto
857 conduce al decremento del contador de ``h2`` y ``h3`` que permanecen en el
858 *live set* ya que sus contadores siguen siendo mayores a 0 (ver figura
859 :vref:`fig:gc-rc-rm-2`).
861 .. fig:: fig:gc-rc-rm-1
863 Ejemplo de conteo de referencias: eliminación de una referencia (parte 1).
865 Eliminación de la referencia ``r0`` :math:`\to` ``h1`` (parte 1).
869 Estado inicial del grafo de conectividad.
876 edge [ color = gray40 ];
886 subgraph cluster_all {
889 label = "root\nset|<r0> r0|<r1> r1",
895 h1 [ label = "h1\n1|<l> l|<r> r" ];
896 h2 [ label = "h2\n2|<l> l|<r> r" ];
897 h3 [ label = "h3\n3|<l> l|<r> r" ];
898 h4 [ label = "h4\n1|<l> l|<r> r" ];
899 h5 [ label = "h5\n1|<l> l|<r> r" ];
900 h6 [ label = "h6\n1|<l> l|<r> r" ];
916 Al ejecutarse ``update(r0, null)``, se comienza por visitar la celda
924 edge [ color = gray40 ];
934 subgraph cluster_all {
937 label = "root\nset|<r0> r0\n*|<r1> r1",
943 h1 [ label = "h1\n1|<l> l|<r> r" ];
944 h2 [ label = "h2\n2|<l> l|<r> r" ];
945 h3 [ label = "h3\n3|<l> l|<r> r" ];
946 h4 [ label = "h4\n1|<l> l|<r> r" ];
947 h5 [ label = "h5\n1|<l> l|<r> r" ];
948 h6 [ label = "h6\n1|<l> l|<r> r" ];
950 root:r0 -> h1 [ style = bold, color = black ];
964 Se decrementa el contador de ``h1`` quedando en 0 (pasa a ser *basura*).
965 Se elimina primero ``h1.l`` y luego ``h1.r``.
972 edge [ color = gray40 ];
982 subgraph cluster_all {
985 label = "root\nset|<r0> r0\n*|<r1> r1",
992 node [ fillcolor = white, fontcolor = black ];
996 h1 [ label = "h1\n0|<l> l|<r> r" ];
997 h2 [ label = "h2\n2|<l> l|<r> r" ];
998 h3 [ label = "h3\n3|<l> l|<r> r" ];
999 h4 [ label = "h4\n1|<l> l|<r> r" ];
1000 h5 [ label = "h5\n1|<l> l|<r> r" ];
1001 h6 [ label = "h6\n1|<l> l|<r> r" ];
1003 root:r0 -> h1 [ style = invis ];
1005 h1:l -> h2 [ style = bold, color = black ];
1016 .. fig:: fig:gc-rc-rm-2
1019 Ejemplo de conteo de referencias: eliminación de una referencia (parte 2).
1021 Eliminación de la referencia ``r0`` :math:`\to` ``h1`` (parte 2).
1025 Se decrementa el contador de ``h2`` pero no queda en 0 (permanece en el
1033 edge [ color = gray40 ];
1043 subgraph cluster_all {
1046 label = "root\nset|<r0> r0\n*|<r1> r1",
1053 node [ fillcolor = white, fontcolor = black ];
1057 h1 [ label = "h1\n0|<l> l|<r> r" ];
1058 h2 [ label = "h2\n1|<l> l|<r> r" ];
1059 h3 [ label = "h3\n3|<l> l|<r> r" ];
1060 h4 [ label = "h4\n1|<l> l|<r> r" ];
1061 h5 [ label = "h5\n1|<l> l|<r> r" ];
1062 h6 [ label = "h6\n1|<l> l|<r> r" ];
1064 root:r0 -> h1 [ style = invis ];
1066 h1:l -> h2 [ style = invis ];
1067 h1:r -> h3 [ style = bold, color = black ];
1078 El contador de ``h3`` tampoco queda en 0, sigue en el *live set*.
1085 edge [ color = gray40 ];
1095 subgraph cluster_all {
1098 label = "root\nset|<r0> r0|<r1> r1",
1105 node [ fillcolor = white, fontcolor = black ];
1109 h1 [ label = "h1\n0|<l> l|<r> r" ];
1110 h2 [ label = "h2\n1|<l> l|<r> r" ];
1111 h3 [ label = "h3\n2|<l> l|<r> r" ];
1112 h4 [ label = "h4\n1|<l> l|<r> r" ];
1113 h5 [ label = "h5\n1|<l> l|<r> r" ];
1114 h6 [ label = "h6\n1|<l> l|<r> r" ];
1116 root:r0 -> h1 [ style = invis ];
1118 h1:l -> h2 [ style = invis ];
1119 h1:r -> h3 [ style = invis ];
1129 Luego se cambia una referencia (en vez de eliminarse) realizándose la
1130 operación ``update(h3.l, h5)``. Para esto primero se incrementa el contador de
1131 referencias de ``h5`` para evitar confundirlo accidentalmente con *basura* si
1132 se elimina alguna celda que apuntaba a ésta. Luego se procede a decrementar el
1133 contador de ``h2`` que queda en 0, transformándose en *basura* (ver figura
1134 :vref:`fig:gc-rc-up-1`).
1136 .. fig:: fig:gc-rc-up-1
1138 Ejemplo de conteo de referencias: actualización de una referencia (parte 1).
1140 Cambio en la referencia ``h2.l`` :math:`\to` ``h2`` a ``h2.l`` :math:`\to`
1145 Comienza ``update(h3.l, h5)``, se incrementa el contador de ``h5``.
1152 edge [ color = gray40 ];
1162 subgraph cluster_all {
1165 label = "root\nset|<r0> r0|<r1> r1",
1172 node [ fillcolor = white, fontcolor = black ];
1176 h1 [ label = "h1\n0|<l> l|<r> r" ];
1177 h2 [ label = "h2\n1|<l> l|<r> r" ];
1178 h3 [ label = "h3\n2|<l> l\n*|<r> r" ];
1179 h4 [ label = "h4\n1|<l> l|<r> r" ];
1180 h5 [ label = "h5\n2|<l> l|<r> r" ];
1181 h6 [ label = "h6\n1|<l> l|<r> r" ];
1183 root:r0 -> h1 [ style = invis ];
1184 h1:l -> h2 [ style = invis ];
1185 h1:r -> h3 [ style = invis ];
1190 h3:l -> h5 [ style = dotted, color = black ];
1198 Luego se procede a visitar las hijas de ``h3``, comenzando por ``h2``.
1205 edge [ color = gray40 ];
1215 subgraph cluster_all {
1218 label = "root\nset|<r0> r0|<r1> r1",
1225 node [ fillcolor = white, fontcolor = black ];
1229 h1 [ label = "h1\n0|<l> l|<r> r" ];
1230 h2 [ label = "h2\n1|<l> l|<r> r" ];
1231 h3 [ label = "h3\n2|<l> l\n*|<r> r" ];
1232 h4 [ label = "h4\n1|<l> l|<r> r" ];
1233 h5 [ label = "h5\n2|<l> l|<r> r" ];
1234 h6 [ label = "h6\n1|<l> l|<r> r" ];
1236 root:r0 -> h1 [ style = invis ];
1237 h1:l -> h2 [ style = invis ];
1238 h1:r -> h3 [ style = invis ];
1242 h3:l -> h2 [ style = bold, color = black ];
1243 h3:l -> h5 [ style = dotted, color = black ];
1251 Se decrementa el contador de ``h2`` y queda en 0 (pasa a ser *basura*).
1252 Se eliminan las referencias a las hijas.
1259 edge [ color = gray40 ];
1269 subgraph cluster_all {
1272 label = "root\nset|<r0> r0|<r1> r1",
1279 node [ fillcolor = white, fontcolor = black ];
1283 h1 [ label = "h1\n0|<l> l|<r> r" ];
1284 h2 [ label = "h2\n1|<l> l|<r> r" ];
1285 h3 [ label = "h3\n2|<l> l\n*|<r> r" ];
1286 h4 [ label = "h4\n1|<l> l|<r> r" ];
1287 h5 [ label = "h5\n2|<l> l|<r> r" ];
1288 h6 [ label = "h6\n1|<l> l|<r> r" ];
1290 root:r0 -> h1 [ style = invis ];
1291 h1:l -> h2 [ style = invis ];
1292 h1:r -> h3 [ style = invis ];
1294 h2:l -> h4 [ style = bold, color = black ];
1296 h3:l -> h2 [ style = invis ];
1297 h3:l -> h5 [ style = dotted, color = black ];
1304 Lo mismo pasa cuando se desciende a ``h4``, pero al descender a ``h5``
1305 y decrementar el contador, éste sigue siendo mayor que 0 (pues ``h3`` va
1306 a apuntar a ``h5``) así que permanece en el *live set*. Finalmente se termina
1307 de actualizar la referencia ``h3.l`` para que apunte a ``h5`` (ver figura
1308 :vref:`fig:gc-rc-up-2`).
1310 .. fig:: fig:gc-rc-up-2
1312 Ejemplo de conteo de referencias: actualización de una referencia (parte 2).
1314 Cambio en la referencia ``h2.l`` :math:`\to` ``h2`` a ``h2.l`` :math:`\to`
1319 Se decrementa el contador de ``h4`` quedando en 0, pasa a ser *basura*.
1320 Se continúa con ``h5``.
1327 edge [ color = gray40 ];
1337 subgraph cluster_all {
1340 label = "root\nset|<r0> r0|<r1> r1",
1347 node [ fillcolor = white, fontcolor = black ];
1351 h1 [ label = "h1\n0|<l> l|<r> r" ];
1352 h2 [ label = "h2\n1|<l> l|<r> r" ];
1353 h3 [ label = "h3\n2|<l> l\n*|<r> r" ];
1354 h4 [ label = "h4\n0|<l> l|<r> r" ];
1355 h5 [ label = "h5\n2|<l> l|<r> r" ];
1356 h6 [ label = "h6\n1|<l> l|<r> r" ];
1358 root:r0 -> h1 [ style = invis ];
1359 h1:l -> h2 [ style = invis ];
1360 h1:r -> h3 [ style = invis ];
1362 h2:l -> h4 [ style = invis ];
1363 h2:r -> h5 [ style = bold, color = black ];
1364 h3:l -> h2 [ style = invis ];
1365 h3:l -> h5 [ style = dotted, color = black ];
1373 Se decrementa el contador de ``h5`` pero sigue siendo mayor que 0.
1380 edge [ color = gray40 ];
1390 subgraph cluster_all {
1393 label = "root\nset|<r0> r0|<r1> r1",
1400 node [ fillcolor = white, fontcolor = black ];
1404 h1 [ label = "h1\n0|<l> l|<r> r" ];
1405 h2 [ label = "h2\n1|<l> l|<r> r" ];
1406 h3 [ label = "h3\n2|<l> l\n*|<r> r" ];
1407 h4 [ label = "h4\n0|<l> l|<r> r" ];
1408 h5 [ label = "h5\n1|<l> l|<r> r" ];
1409 h6 [ label = "h6\n1|<l> l|<r> r" ];
1411 root:r0 -> h1 [ style = invis ];
1412 h1:l -> h2 [ style = invis ];
1413 h1:r -> h3 [ style = invis ];
1415 h2:l -> h4 [ style = invis ];
1416 h2:r -> h5 [ style = invis ];
1417 h3:l -> h5 [ style = bold, color = black ];
1418 h3:l -> h2 [ style = invis ];
1426 Se termina por actualizar la referencia de ``h3.l`` para que apunte
1434 edge [ color = gray40 ];
1444 subgraph cluster_all {
1447 label = "root\nset|<r0> r0|<r1> r1",
1454 node [ fillcolor = white, fontcolor = black ];
1458 h1 [ label = "h1\n0|<l> l|<r> r" ];
1459 h1 [ label = "h1\n0|<l> l|<r> r" ];
1460 h2 [ label = "h2\n0|<l> l|<r> r" ];
1461 h3 [ label = "h3\n2|<l> l|<r> r" ];
1462 h4 [ label = "h4\n0|<l> l|<r> r" ];
1463 h5 [ label = "h5\n1|<l> l|<r> r" ];
1464 h6 [ label = "h6\n1|<l> l|<r> r" ];
1466 root:r0 -> h1 [ style = invis ];
1467 h1:l -> h2 [ style = invis ];
1468 h1:r -> h3 [ style = invis ];
1470 h2:l -> h4 [ style = invis ];
1471 h2:r -> h5 [ style = invis ];
1473 h3:l -> h2 [ style = invis ];
1480 Finalmente se presenta lo que sucede cuando se elimina la última referencia
1481 a un ciclo (en este caso un ciclo simple de 2 celdas: ``h3`` y ``h6``). Se
1482 elimina la única referencia externa al ciclo (``r1``), por lo que se visita la
1483 celda ``h3`` decrementando su contador de referencias, pero éste continúa
1484 siendo mayor que 0 porque la celda ``h6`` (parte del ciclo) la referencia. Por
1485 lo tanto el ciclo, y todas las celdas a las que apunta que no tienen otras
1486 referencias externas y por lo tanto deberían ser *basura* también (``h5``), no
1487 pueden ser recicladas y su memoria es perdida (ver figura
1488 :vref:`fig:gc-rc-cycle`).
1490 .. fig:: fig:gc-rc-cycle
1493 Ejemplo de conteo de referencias: pérdida de memoria debido a un ciclo.
1495 Eliminación de la referencia ``r1`` :math:`\to` ``h3`` (pérdida de memoria
1500 El ejecutarse ``update(r1, null)`` se visita la celda ``h3``.
1507 edge [ color = gray40 ];
1517 subgraph cluster_all {
1520 label = "root\nset|<r0> r0|<r1> r1\n*",
1527 node [ fillcolor = white, fontcolor = black ];
1531 h1 [ label = "h1\n0|<l> l|<r> r" ];
1532 h1 [ label = "h1\n0|<l> l|<r> r" ];
1533 h2 [ label = "h2\n0|<l> l|<r> r" ];
1534 h3 [ label = "h3\n2|<l> l|<r> r" ];
1535 h4 [ label = "h4\n0|<l> l|<r> r" ];
1536 h5 [ label = "h5\n1|<l> l|<r> r" ];
1537 h6 [ label = "h6\n1|<l> l|<r> r" ];
1539 root:r0 -> h1 [ style = invis ];
1540 h1:l -> h2 [ style = invis ];
1541 h1:r -> h3 [ style = invis ];
1542 root:r1 -> h3 [ style = bold, color = black ];
1543 h2:l -> h4 [ style = invis ];
1544 h2:r -> h5 [ style = invis ];
1546 h3:l -> h2 [ style = invis ];
1554 Se decrementa el contador de ``h3`` pero sigue siendo mayor que 0 por el
1562 edge [ color = gray40 ];
1572 subgraph cluster_all {
1575 label = "root\nset|<r0> r0|<r1> r1\n*",
1582 node [ fillcolor = white, fontcolor = black ];
1586 h1 [ label = "h1\n0|<l> l|<r> r" ];
1587 h1 [ label = "h1\n0|<l> l|<r> r" ];
1588 h2 [ label = "h2\n0|<l> l|<r> r" ];
1589 h3 [ label = "h3\n1|<l> l|<r> r" ];
1590 h4 [ label = "h4\n0|<l> l|<r> r" ];
1591 h5 [ label = "h5\n1|<l> l|<r> r" ];
1592 h6 [ label = "h6\n1|<l> l|<r> r" ];
1594 root:r0 -> h1 [ style = invis ];
1595 h1:l -> h2 [ style = invis ];
1596 h1:r -> h3 [ style = invis ];
1597 root:r1 -> h3 [ style = invis ];
1598 h2:l -> h4 [ style = invis ];
1599 h2:r -> h5 [ style = invis ];
1601 h3:l -> h2 [ style = invis ];
1612 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1614 Este algoritmo es el más parecido a la teoría sobre recolección de basura.
1615 Consiste en realizar la recolección en 2 fases: marcado y barrido. La primera
1616 fase consiste en el proceso de marcar el grafo de conectividad del *heap* para
1617 descubrir qué celdas son alcanzables desde el *root set*, tal y como se
1618 describió en :ref:`gc_intro_mark`.
1620 Una vez marcadas todas las celdas, se sabe que las celdas *blancas* son
1621 *basura*, por lo tanto el paso que queda es el *barrido* de estas celdas,
1622 liberándolas. Esto se efectúa recorriendo todo el *heap*. Por lo tanto cada
1623 recolección es :math:`O(\lvert Heap \rvert)`, a diferencia del conteo de
1624 referencia que dijimos que en el peor caso es :math:`O(\lvert Live \thickspace
1625 set \rvert)`. Sin embargo el conteo de referencias se ejecuta **cada vez que
1626 se actualiza una referencia** mientras que la recolección en el marcado
1627 y barrido se realiza típicamente solo cuando el *mutator* pide una celda pero
1628 no hay ninguna libre. Esto hace que la constante del conteo de referencias sea
1629 típicamente varios órdenes de magnitud mayores que en el marcado y barrido.
1631 A continuación se presentan los servicios básicos de este algoritmo::
1642 function collect() is
1646 function sweep_phase() is
1647 foreach cell in heap
1653 El algoritmo ``mark_sweep()`` es exactamente igual al presentado en
1654 :ref:`gc_intro_mark`. Es preciso notar que la fase de barrido
1655 (``sweep_phase()``) debe tener una comunicación extra con el *low level
1656 allocator* para poder obtener todas las celdas de memoria que existen en el
1659 A diferencia del conteo de referencias, este algoritmo es :ref:`indirecto
1660 <gc_direct>` y :ref:`no incremental <gc_inc>`, ya que se realiza un recorrido
1661 de todo el *heap* de forma espaciada a través de la ejecución del programa. En
1662 general el *mutator* sufre pausas considerablemente mayores (en promedio) que
1663 con el conteo de referencias, lo que puede ser problemático para aplicaciones
1664 con requerimientos rígidos de tiempo, como aplicaciones *real-time*. Debido
1665 a la percepción de las pausas grandes, este tipo de colectores se conocen como
1666 :ref:`stop-the-world <gc_concurrent>` (o *detener el mundo*).
1668 Una ventaja fundamental sobre el conteo de referencias es la posibilidad de
1669 reclamar estructuras cíclicas sin consideraciones especiales. Podemos observar
1670 como esto es posible analizando el ejemplo en las figuras :r:`fig:gc-mark-1`
1671 y :vref:`fig:gc-mark-2`. Si se eliminaran las referencias :math:`r0 \to h1`
1672 y :math:`h6 \to h2`, la fase de marcado consistiría solamente en marcar la
1673 celda :math:`h6`, pues es la única alcanzable desde el *root set*. Todas las
1674 demás celdas permanecerían blancas y por lo tanto pueden ser liberadas sin
1675 inconvenientes en la fase de barrido, que recorre el *heap* linealmente.
1681 Copia de semi-espacio
1682 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1684 Este algoritmo consiste en hacer una partición del *heap* en 2 mitades
1685 o *semi-espacios*, llamados usualmente *Fromspace* y *Tospace*. El primero se
1686 utiliza para asignar nuevas celdas de forma lineal, asumiendo un *heap*
1687 contiguo, incrementando un puntero (ver figura :vref:`fig:gc-copy`). Esto se
1688 conoce como *pointer bump allocation* y es, probablemente, la forma más
1689 eficiente de asignar memoria (tan eficiente como asignar memoria en el
1692 .. fig:: fig:gc-copy
1694 Estructura del *heap* de un recolector con copia de semi-espacios.
1700 zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
1702 /---+"Fromspace" /---+"Tospace"
1704 V_______________________________V_______________________________
1705 | XXXX X XXX aaaaaaaaaaaaaaaa|bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb|
1706 | XXXX X XXX aaaaaaaaaaaaaaaa|bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb|
1707 | XXXX X XXX aaaaaaaaaaaaaaaa|bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb|
1708 |~~~~~~~~~~~~~~~A~~~~~~~~~~~~~~~|~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1710 | | | XX "Fromspace usado"
1712 | | ZZ "Fromspace basura"
1714 |/ "longitud del semi-espacio" |/ AA "Fromspace libre"
1715 +- - - - - - - - - - - - - - - -+
1719 La segunda mitad (*Tospace*) permanece inutilizada hasta que se agota el
1720 espacio en el *Fromspace*; en ese momento comienza el proceso de recolección
1721 de basura que consiste en recorrer el grafo de conectividad, copiando las
1722 celdas *vivas* del *Fromspace* al *Tospace* de manera contigua, como si
1723 estuvieran asignando por primera vez. Como la posición en memoria de las
1724 celdas cambia al ser movidas, es necesario actualizar la dirección de memoria
1725 de todas las celdas *vivas*. Para esto se almacena una dirección de memoria de
1726 redirección, *forwarding address*, en las celdas que mueven. La *forwarding
1727 address* sirve a su vez de marca, para no recorrer una celda dos veces (como
1728 se explica en :ref:`gc_intro_mark`). Cuando se encuentra una celda que ya fue
1729 movida, simplemente se actualiza la referencia por la cual se llegó a esa
1730 celda para que apunte a la nueva dirección, almacenada en la *forwarding
1731 address*. Una vez finalizado este proceso, el *Fromspace* y *Tospace*
1732 invierten roles y se prosigue de la misma manera (todo lo que quedó en el
1733 viejo *Fromspace* es *basura* por definición, por lo que se convierte el
1736 A continuación se presenta una implementación sencilla de los servicios
1737 provistos por este tipo de recolectores. Cabe destacar que este tipo de
1738 recolectores deben estar íntimamente relacionados con el *low level
1739 allocator*, ya que la organización del *heap* y la forma de asignar memoria es
1740 parte fundamental de este algoritmo. Se asume que ya hay dos áreas de memoria
1741 del mismo tamaño destinadas al *Fromspace* y *Tospace*, y la existencia de
1742 4 variables: ``fromspace`` (que apunta a la base del *Fromspace*), ``tospace``
1743 (que apunta a la base del *Tospace*), ``spacesize`` (que contiene el tamaño de
1744 un semi-espacio) y ``free`` (que apunta al lugar del *Fromspace* donde
1745 comienza la memoria libre). También vale aclarar que este algoritmo soporta
1746 inherentemente celdas de tamaño variable, por lo que los servicios ``alloc()``
1747 y ``new()`` [#gccopynew]_ reciben como parámetro el tamaño de la celda
1750 function alloc(size) is
1751 if free + size > fromspace + spacesize
1758 function new(size) is
1767 function collect() is
1769 foreach r in root_set
1771 fromspace, tospace = tospace, fromspace
1773 function copy(cell) is
1774 if cell.forwarding_address is null
1775 cell.forwarding_address = free
1776 free = free + cell.size
1777 foreach child in cell
1779 return cell.forwarding_address
1781 return cell.forwarding_address
1783 .. [#gccopynew] Notar que ``new()`` es igual que en el marcado y barrido con
1784 la salvedad de que en este caso toma como parámetro el tamaño de la celda.
1786 Esta técnica tiene nombres variados en inglés: *semi-space*, *two-space*
1787 o simplemente *copying collector*. En este documento se denomina "copia de
1788 semi-espacio" porque los otros nombres son demasiado generales y pueden
1789 describir, por ejemplo, algoritmos donde no hay copia de celdas o donde no hay
1790 2 semi-espacios (como se verá en :ref:`gc_art`).
1792 Al igual que el :ref:`gc_mark_sweep` este algoritmo es :ref:`indirecto
1793 <gc_direct>`, :ref:`no incremental <gc_inc>` y :ref:`stop-the-world
1794 <gc_concurrent>`. Las diferencias con los esquemas vistos hasta ahora son
1795 evidentes. La principal ventaja sobre el marcado y barrido (que requiere una
1796 pasada sobre el *live set*, el marcado, y otra sobre el *heap* entero, el
1797 barrido) es que este método require una sola pasada y sobre las celdas vivas
1798 del *heap* solamente. La principal desventaja es copia memoria, lo que puede
1799 ser particularmente costoso, además de requerir, como mínimo, el doble de
1800 memoria de lo que el *mutator* realmente necesita. Esto puede traer en
1801 particular problemas con la memoria virtual y el caché, por la pobre localidad
1804 Por lo tanto los recolectores de este tipo pueden ser convenientes por sobre
1805 el marcado y barrido cuando se espera que el *live set* sea muy pequeño luego
1806 de una recolección. En estos casos el trabajo realizado por este tipo de
1807 recolectores puede ser considerablemente menor que el del marcado y barrido.
1808 Y por el contrario, si el *working set* es pequeño, al ser *compactado* en
1809 memoria puede mejorar la localidad de referencia (si el *working set* es
1810 grande se corre el riesgo de que la localidad de referencia empeore al moverse
1817 A continuación se presenta un sencillo ejemplo del algoritmo. Se parte de una
1818 estructura simple con 4 celdas en el *Fromspace* (que incluye un pequeño ciclo
1819 para mostrar que este algoritmo tampoco tiene inconvenientes para
1820 recolectarlos). Asumimos que ya no queda lugar en el *Fromspace* por lo que
1821 comienza la ejecución de ``collect()``. Se comienza por el *root set* que
1822 apunta a ``h3``, por lo tanto ésta es movida al *Tospace* primero, dejando una
1823 *forwarding address* a la nueva ubicación (ver figura
1824 :vref:`fig:gc-copy-ex-1`).
1826 .. fig:: fig:gc-copy-ex-1
1828 Ejemplo de recolección con copia de semi-espacios (parte 1).
1832 Estructura inicial del *heap*. El *Fromspace* está complete y se inicial
1838 +--------------------------------------------------+
1840 | /--------------------------------\ |
1841 | | /--------\ /------\ | |
1843 | ______|_V________|__V______|___________V______ |
1844 | ZZZZGGGGGGGGZZZZGGGGGGGGggggggggZZZZGGGGGGGGZZZZ |
1845 | ZZZZGGGGGGGGZZZZGGGGGGGGggggggggZZZZGGGGGGGGZZZZ |
1846 | ~~~~~~~~~|~~~~~~~~~~A~~~~~~~A~~~~~~~~~~~~~~~~~ |
1847 | h1 | h2 | h3 | h4 |
1849 | \----+"root set" |
1853 | ______________________________________________ |
1854 | BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB |
1855 | BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB |
1856 | A~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
1860 +--------------------------------------------------+
1864 Se sigue la referencia del *root set*, copiando ``h3`` al *Tospace*
1865 y dejando una *forwarding address*.
1870 +--------------------------------------------------+
1872 | /--------------------------------\ |
1873 | | /--------\ /------\ | |
1875 | ______|_V________|__V______|___________V______ |
1876 | ZZZZGGGGGGGGZZZZGGGGGGGGffffffffZZZZGGGGGGGGZZZZ |
1877 | ZZZZGGGGGGGGZZZZGGGGGGGGffffffffZZZZGGGGGGGGZZZZ |
1878 | ~~~~~~~~~|~~~~~~~~~~A~~~~~~~|A~~~~~~~~~~~~~~~~ |
1879 | h1 | h2 | h3 || h4 |
1881 | +\----+"root set" |
1883 | /-------------------------+ |
1885 | V_____________________________________________ |
1886 | HHHHHHHHBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB |
1887 | HHHHHHHHBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB |
1888 | ~~~~~~~A~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
1892 +--------------------------------------------------+
1895 A continuación se copian las *hijas* de ``h3``, en este caso sólo ``h2``, que
1896 se ubica en el *Tospace* a continuación de ``h3``, dejando nuevamente su
1897 ``forwarding address`` en la celda original. Al proceder recursivamente, se
1898 procede a copiar ``h1`` al *Tospace*, dejando una vez más la *forwarding
1899 address* en la celda original y procediendo con las hijas. Aquí podemos
1900 observar que al seguirse la referencia :math:`h1 \to h2`, como ``h2`` ya había
1901 sido visitada, solamente se actualiza la referencia apuntando a la nueva
1902 ubicación de ``h2`` pero no se vuelve a copiar la celda (ver figura
1903 :vref:`fig:gc-copy-ex-2`).
1905 .. fig:: fig:gc-copy-ex-2
1907 Ejemplo de recolección con copia de semi-espacios (parte 2).
1911 Se sigue :math:`h3 \to h2`, copiando ``h2`` al *Tospace* y dejando una
1912 *forwarding address*.
1917 +--------------------------------------------------+
1919 | /--------------------------------\ |
1920 | | /--------\ /------\ | |
1922 | ______|_V________|__V______|___________V______ |
1923 | ZZZZGGGGGGGGZZZZFFFFFFFFffffffffZZZZGGGGGGGGZZZZ |
1924 | ZZZZGGGGGGGGZZZZFFFFFFFFffffffffZZZZGGGGGGGGZZZZ |
1925 | ~~~~~~~~~|~~~~~~~~~~A|~~~~~~|A~~~~~~~~~~~~~~~~ |
1926 | h1 | h2 || h3 || h4 |
1927 | \----------/+ || |
1928 | / +\----+"root set" |
1930 | /------+------------------+ |
1932 | V______V______________________________________ |
1933 | HHHHHHHHhhhhhhhhBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB |
1934 | HHHHHHHHhhhhhhhhBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB |
1935 | ~~|~~~~~~A~~~~~A~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
1937 | \------/ \----+"free" |
1939 +--------------------------------------------------+
1943 Se sigue :math:`h2 \to h1`, copiando ``h1``. Luego :math:`h1 \to h2`
1944 pero ``h2`` no se copia, sólo se actualiza la referencia con la
1945 *forwarding address*.
1950 +--------------------------------------------------+
1952 | /--------------------------------\ |
1953 | | /--------\ /------\ | |
1955 | ______|_V________|__V______|___________V______ |
1956 | ZZZZFFFFFFFFZZZZFFFFFFFFffffffffZZZZGGGGGGGGZZZZ |
1957 | ZZZZFFFFFFFFZZZZFFFFFFFFffffffffZZZZGGGGGGGGZZZZ |
1958 | ~~~~~~~|~|~~~~~~~~~~A|~~~~~~|A~~~~~~~~~~~~~~~~ |
1959 | h1 | | h2 || h3 || h4 |
1960 | \-+----------/+ || |
1961 | +-----+ / +\-----+"root set" |
1963 | /------+-------+----------+ |
1965 | V______V_______V______________________________ |
1966 | HHHHHHHHhhhhhhhhHHHHHHHHBBBBBBBBBBBBBBBBBBBBBBBB |
1967 | HHHHHHHHhhhhhhhhHHHHHHHHBBBBBBBBBBBBBBBBBBBBBBBB |
1968 | ~~|~~~~~~A~|~A~~|~A~~~~A~~~~~~~~~~~~~~~~~~~~~~ |
1970 | \------/ | \--/ | \----+"free" |
1971 | "Tospace" \------/ |
1972 +--------------------------------------------------+
1975 Se termina de copiar recursivamente las hijas de ``h1`` al copiar ``h4`` que
1976 resulta la última celda (sin hijas). Finalmente se invierten los roles de los
1977 semi-espacios y se actualiza la referencia del *root set* para que apunte a la
1978 nueva ubicación de ``h3``, como se muestra en la figura
1979 :vref:`fig:gc-copy-ex-3`.
1981 .. fig:: fig:gc-copy-ex-3
1983 Ejemplo de recolección con copia de semi-espacios (parte 3).
1987 Se sigue :math:`h1 \to h4` copiando `h4`` al *Tospace* y dejando una
1988 *forwarding address*.
1993 +--------------------------------------------------+
1995 | /--------------------------------\ |
1996 | | /--------\ /------\ | |
1998 | ______|_V________|__V______|___________V______ |
1999 | ZZZZFFFFFFFFZZZZFFFFFFFFffffffffZZZZFFFFFFFFZZZZ |
2000 | ZZZZFFFFFFFFZZZZFFFFFFFFffffffffZZZZFFFFFFFFZZZZ |
2001 | ~~~~~~~|~|~~~~~~~~~~A|~~~~~~|A~~~~~~~~~~|~~~~~ |
2002 | h1 | | h2 || h3 || h4 \----\ |
2003 | \-+----------/+ || | |
2004 | +-----+ / +----/\---+"root set" | |
2005 | +-------+---+ / | |
2006 | /------+-------+-----+ /--------------------/ |
2007 | | h3 | h2 | h1 | h4 |
2008 | V______V_______V________V_____________________ |
2009 | HHHHHHHHhhhhhhhhHHHHHHHHhhhhhhhhBBBBBBBBBBBBBBBB |
2010 | HHHHHHHHhhhhhhhhHHHHHHHHhhhhhhhhBBBBBBBBBBBBBBBB |
2011 | ~~|~~~~~~A~|~A~~|~A~|~~~~~~A~~~A~~~~~~~~~~~~~~ |
2012 | | | | | | | | | | |
2013 | \------/ | \--/ | \------/ \----+"free" |
2014 | "Tospace" \------/ |
2015 +--------------------------------------------------+
2019 Se finaliza la recolección, se intercambian los roles de los
2020 semi-espacios y se actualiza la referencia del *root set*.
2025 +--------------------------------------------------+
2030 | ______________________________________________ |
2031 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA |
2032 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA |
2033 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
2040 | V______________________________________________ |
2041 | HHHHHHHHhhhhhhhhHHHHHHHHhhhhhhhhBBBBBBBBBBBBBBBB |
2042 | HHHHHHHHhhhhhhhhHHHHHHHHhhhhhhhhBBBBBBBBBBBBBBBB |
2043 | ~~|~~~~~~A~|~A~~|~A~|~~~~~~A~~~A~~~~~~~~~~~~~~ |
2044 | | | | | | | | | | |
2045 | \------/ | \--/ | \------/ \---+"free" |
2046 | "Fromspace" \------/ |
2047 +--------------------------------------------------+
2054 ----------------------------------------------------------------------------
2056 La manera en que la investigación sobre recolección de basura ha crecido es
2057 realmente sorprendente. Hay, al menos, 2995 publicaciones sobre recolección de
2058 basura registradas al momento de escribir este documento [GCBIB]_. Esto hace
2059 que el análisis del estado del arte sea particularmente complejo y laborioso.
2061 Analizar todas las publicaciones existentes es algo excede los objetivos de
2062 este trabajo, por lo tanto se analizó solo una porción significativa,
2063 utilizando como punto de partida a [JOLI96]_.
2065 De este análisis se observó que la gran mayoría de los algoritmos son
2066 combinaciones de diferentes características básicas; por lo tanto se intentó
2067 aislar estas características que son utilizadas como bloques de construcción
2068 para algoritmos complejos. Ésta tampoco resultó ser una tarea sencilla debido
2069 a que muchos de estos bloques de construcción básicos están interrelacionados
2070 y encontrar una división clara para obtener características realmente atómicas
2073 La construcción de recolectores más complejos se ve alimentada también por la
2074 existencia de recolectores *híbridos*; es decir, recolectores que utilizan más
2075 de un algoritmo dependiendo de alguna característica de la memoria
2076 a administrar. No es poco común observar recolectores que utilizan un
2077 algoritmo diferente para celdas que sobreviven varias recolecciones que para
2078 las que mueren rápidamente, o que usan diferentes algoritmos para objetos
2079 pequeños y grandes, o que se comporten de forma conservativa para ciertas
2080 celdas y sean precisos para otras.
2082 De todas estas combinaciones resulta el escenario tan fértil para la
2083 investigación sobre recolección de basura.
2085 A continuación se presentan las principales clases de algoritmos
2086 y características básicas encontradas durante la investigación del estado del
2087 arte. La separación de clases y aislamiento de características no es siempre
2088 trivial, ya que hay ciertas clases de recolectores que están interrelacionadas
2089 (o ciertas características pueden estar presentes sólo en recolectores de una
2090 clase en particular).
2096 Recolección directa / indirecta
2097 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2099 Generalmente se llama recolección **directa** a aquella en la cual el
2100 compilador o lenguaje instrumenta al *mutator* de forma tal que la información
2101 sobre el grafo de conectividad se mantenga activamente cada vez que hay un
2102 cambio en él. Normalmente se utiliza un contador de referencia en cada celda
2103 para este propósito, permitiendo almacenar en todo momento la cantidad de
2104 nodos que apuntan a ésta (ver :ref:`gc_rc`). Esto permite reclamar una celda
2105 instantáneamente cuando el *mutator* deja de hacer referencia a ella. Este
2106 tipo de recolectores son inherentemente :ref:`incrementales <gc_inc>`.
2108 Por el contrario, los recolectores **indirectos** normalmente no interfieren
2109 con el *mutator* en cada actualización del grafo de conectividad (exceptuando
2110 algunos :ref:`recolectores incrementales <gc_inc>` que a veces necesitan
2111 instrumentar el *mutator* pero no para mantener el estado del grafo de
2112 conectividad completo). La recolección se dispara usualmente cuando el
2113 *mutator* requiere asignar memoria pero no hay más memoria libre conocida
2114 disponible y el recolector se encarga de generar la información de
2115 conectividad desde cero para determinar qué celdas son *basura*.
2117 Esta es la madre de toda clasificación, también conocidos como :ref:`conteo de
2118 referencias <gc_rc>` (directa) y *traicing garbage collection* (indirecta).
2119 Prácticamente todos los recolectores menos el :ref:`conteo de referencias
2120 <gc_rc>` están dentro de esta última categoría (como por ejemplo, el
2121 :ref:`marcado y barrido <gc_mark_sweep>` y :ref:`copia de semi-espacio
2124 Otros ejemplos de recolectores modernos *directos* son el recolector de basura
2125 de Python_ [NAS00]_ y [LINS05]_ (aunque ambos tiene un algoritmo *indirecto*
2126 para recuperar ciclos).
2132 Recolección incremental
2133 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2135 Recolección incremental es aquella que se realiza de forma intercalada con el
2136 *mutator*. En general el propósito es disminuir el tiempo de las pausas
2137 causadas por el recolector (aunque generalmente el resultado es un mayor costo
2138 total de recolección en términos de tiempo).
2140 De los `algoritmos clásicos`_ el único que es incremental en su forma más
2141 básica es el :ref:`conteo de referencias <gc_rc>`. Otros recolectores pueden
2142 hacerse incrementales de diversas maneras, pero en general consta de hacer
2143 parte del trabajo de escanear el grafo de conectividad cada vez que el
2144 *mutator* asigna memoria. En general para hacer esto es también necesario
2145 instrumentar al *mutator* de forma tal que informe al recolector cada vez que
2146 cambia el grafo de conectividad, para que éste pueda marcar al sub-grafo
2147 afectado por el cambio como *desactualizado* y así re-escanearlo nuevamente en
2148 la próxima iteración. Para realizar esto en recolectores :ref:`indirectos
2149 <gc_direct>` se utiliza la :ref:`abstracción tricolor <gc_intro_tricolor>`;
2150 cuando el *mutator* cambia una referencia, se marca *gris* la celda que la
2151 contiene, de modo que el recolector vuelva a visitarla.
2153 En general la eficiencia de los recolectores incrementales disminuye
2154 considerablemente cuando el *mutator* actualiza muy seguido el grafo de
2155 conectividad, porque debe re-escanear sub-grafos que ya había escaneado una
2156 y otra vez. A esto se debe también que en general el tiempo de procesamiento
2157 total de un recolector incremental sea mayor que uno no incremental, aunque el
2158 tiempo de pausa de una recolección sea menor.
2160 Ejemplos de recolectores que se encuentran dentro de esta categoría son
2161 [BOEH91]_, [LINS05]_,
2167 Recolección concurrente / paralela / *stop-the-world*
2168 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2170 Los recolectores concurrentes son aquellos que pueden correr en paralelo con
2171 el *mutator*. Por el contrario, aquellos que pausan el *mutator* para realizar
2172 la recolección son usualmente denominados *stop-the-world* (*detener el
2173 mundo*), haciendo referencia a que pausan todos los hilos del *mutator* para
2174 poder escanear el grafo de conectividad de forma consistente. Hay una tercera
2175 clase de colectores que si bien son *stop-the-world*, utilizan todos los hilos
2176 disponibles para realizar la recolección (ver figura
2177 :vref:`fig:gc-concurrent`).
2179 .. fig:: fig:gc-concurrent
2181 Distintos tipos de recolectores según el comportamiento en ambientes
2191 ___________________________________________________________________
2193 | HHHHHHHHHZZZZZZZZZZZZZHHHHHHHHHHHHZZZZZZZZZZZZZHHHHHHHHHHHHHHHHHH |
2195 | HHHHHHHHHZZZZZZZZZZZZZHHHHHHHHHHHHXXXXXXXXXXXXXHHHHHHHHHHHHHHHHHH |
2197 | HHHHHHHHHXXXXXXXXXXXXXHHHHHHHHHHHHZZZZZZZZZZZZZHHHHHHHHHHHHHHHHHH |
2199 | HH Mutator ZZ Inactivo XX Recolector |
2200 |___________________________________________________________________|
2209 ___________________________________________________________________
2211 | HHHHHHHHHXXXXXHHHHHHHHHHHHHHHHHHHHXXXXXHHHHHHHHHHHHHHHHHHHHHHHHHH |
2213 | HHHHHHHHHXXXXXHHHHHHHHHHHHHHHHHHHHXXXXXHHHHHHHHHHHHHHHHHHHHHHHHHH |
2215 | HHHHHHHHHXXXXXHHHHHHHHHHHHHHHHHHHHXXXXXHHHHHHHHHHHHHHHHHHHHHHHHHH |
2217 | HH Mutator ZZ Inactivo XX Recolector |
2218 |___________________________________________________________________|
2227 ___________________________________________________________________
2229 | HHHHHHHHHZZHHHHHHHHHHHHHHHHHHHHHHHZZHHHHHHHHHHHHHHHHHHHHHHHHHHHHH |
2231 | HHHHHHHHHZZHHHHHHHHHHHHHHHHHHHHHHHZZHHHHHHHHHHHHHHHHHHHHHHHHHHHHH |
2233 | ZZZZZZZZZXXXXXXXXXXXXXXXZZZZZZZZZZXXXXXXXXXXXXXXXZZZZZZZZZZZZZZZZ |
2235 | HH Mutator ZZ Inactivo XX Recolector |
2236 |___________________________________________________________________|
2239 Para lograr que un recolector sea concurrente generalmente el mecanismo es
2240 similar al necesario para hacer un :ref:`recolector incremental <gc_inc>`: hay
2241 que instrumentar al *mutator* para que informe al recolector cuando se realiza
2242 algún cambio en el grafo de conectividad, de forma tal que pueda volver
2243 a escanear el sub-grafo afectado por el cambio.
2245 Esto también trae como consecuencia el incremento en el tiempo total que
2246 consume el recolector, debido a la necesidad de re-escanear sub-grafos que han
2247 sido modificados, además de la sincronización necesaria entre *mutator*
2250 ¿Cúal es la idea entonces de un recolector concurrente? Una vez más, al igual
2251 que los recolectores incrementales, el principal objetivo es disminuir las
2252 largas pausas provocadas por la recolección de basura. Sin embargo, este tipo
2253 de algoritmos además permite hacer un mejor aprovechamiento de las
2254 arquitecturas *multi-core* [#gcmulticore]_ que cada vez se afirman más, ya que
2255 el *mutator* y el recolector pueden estar corriendo realmente en paralelo,
2256 cada uno en un procesador distinto. Algunos recolectores van más allá
2257 y permiten incluso paralelizar la recolección de basura en varios hilos
2258 ([HUEL98]_, [LINS05]_). Otros ejemplos de recolectores concurrentes (aunque no
2259 ofrece paralelización del procesamiento del recolector en varios hilos) son
2260 [BOEH91]_, [RODR97]_.
2262 .. [#gcmulticore] Una arquitectura *multi-core* es aquella que combina dos
2263 o más núcleos (*cores*) independientes que trabajan a la misma frecuencia,
2264 pero dentro de un solo circuito integrado o procesador.
2266 Todos los :ref:`algoritmos clásicos <gc_classic>` que se han citado son del
2267 tipo *stop-the-world*.
2273 Lista de libres / *pointer bump allocation*
2274 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2276 Esta clasificación se refiere principalmente a la forma en que se organiza el
2277 *heap*, íntimamente relacionado al *low level allocator*. Si bien se ha dicho
2278 que en este trabajo no se prestará particular atención a este aspecto, en
2279 ciertos algoritmos es tan relevante que tampoco es sensato pasarlo por alto
2282 En términos generales, hay dos formas fundamentales de organizar el *heap*,
2283 manteniendo una lista de libres o realizando *pointer bump allocation*, como
2284 se explicó en :ref:`gc_copy`. La primera forma consiste, a grandes rasgos, en
2285 separar el *heap* en celdas (que pueden agruparse según tamaño) y enlazarlas
2286 en una lista de libres. Al solicitarse una nueva celda simplemente se la
2287 desenlaza de la lista de libres. Por otro lado, cuando el recolector detecta
2288 una celda *muerta*, la vuelve a enlazar en la lista de libres. Este es un
2289 esquema simple pero con limitaciones, entre las principales, el costo de
2290 asignar puede ser alto si hay muchos tamaños distintos de celda y soportar
2291 tamaño de celda variable puede ser complejo o acarrear muchas otras
2292 ineficiencias. :ref:`gc_mark_sweep` en general usa este esquema, al igual que
2295 Otro forma de organizar el *heap* es utilizándolo como una especie de *stack*
2296 en el cual para asignar simplemente se incrementa un puntero. Este esquema es
2297 simple y eficiente, si el recolector puede mover celdas (ver
2298 :ref:`gc_moving`); de otra manera asignar puede ser muy costoso si hay que
2299 buscar un *hueco* en el heap (es decir, deja de reducirse a incrementar un
2300 puntero). El clásico ejemplo de esta familia es :ref:`gc_copy`.
2302 Sin embargo, entre estos dos extremos, hay todo tipo de híbridos. Existen
2303 recolectores basados en *regiones*, que se encuentran en un punto intermedio.
2304 Dentro de una región se utiliza un esquema de *pointer bump allocation* pero
2305 las regiones en sí se administran como una lista de libres (como por ejemplo
2306 [BLAC08]_). Otra variación (más común) de este esquema son los *two level
2307 allocators* que asignan páginas completas (similar a las regiones) y dentro de
2308 cada página se asignan las celdas. Ambas, páginas y celdas, se administran
2309 como listas de libres (ejemplos que utilizan este esquema son [BOEHWD]_ y el
2310 :ref:`recolector actual de D <dgc_actual>`).
2316 Movimiento de celdas
2317 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2319 Otra característica muy importante del recolector de basura es si mueve las
2320 celdas o no. En general el movimiento de celdas viene de la mano del esquema
2321 de :ref:`pointer bump allocation <gc_free_list>`, ya que *compacta* todas las
2322 celdas *vivas* al comienzo del *heap* luego de una recolección, permitiendo
2323 este esquema para asignar nuevas celdas, pero puede utilizarse en esquemas
2324 híbridos como recolectores basados en *regiones* (por ejemplo [BLAC08]_).
2326 Además los recolectores con movimiento de celdas deben ser :ref:`precisos
2327 <gc_conserv>`, porque es necesario tener la información completa de los tipos
2328 para saber cuando actualizar los punteros (de otra manera se podría escribir
2329 un dato de una celda que no era un puntero). Para que un recolector pueda
2330 mover celdas, aunque sea parcialmente, en recolectores *semi-precisos* se
2331 utiliza un método conocido como *pinning* (que significa algo como *pinchar
2332 con un alfiler*); una celda es *pinned* (*pinchada*) cuando hay alguna
2333 referencia no-precisa a ella, y por lo tanto no puede ser movida (porque no se
2334 puede estar seguro si es posible actualizar dicha referencia).
2336 La ventaja principal de los colectores con movimiento es la posibilidad de
2337 utilizar :ref:`pointer bump allocation <gc_free_list>` y que es sencillo
2338 implementar recolectores :ref:`generacionales <gc_part>` sobre estos.
2340 De los algoritmos clásicos sólo la :ref:`copia de semi-espacios <gc_copy>`
2341 mueve celdas, el :ref:`conteo de referencias <gc_rc>` y :ref:`marcado
2342 y barrido <gc_mark_sweep>` no lo hacen. Además hay otro algoritmo bastante
2343 básico que mueve celdas, el **marcado y compactado**. Éste no tiene
2344 2 semi-espacios, directamente mueve las celdas compactándolas al comienzo del
2345 *heap*. El algoritmo es un poco más complejo que la :ref:`copia de
2346 semi-espacios <gc_copy>` pero suele poder proveer una mayor localidad de
2347 referencia y *desperdicia* un semi-espacio que está inutilizado salgo en el
2348 momento de la recolección. Por ejemplo para Mono_, que antes usaba un
2349 recolector conservativo sin movimiento ([BOEHWD]_) se está implementando un
2350 recolector de este tipo [MOLAWE]_ [MOLA06]_.
2356 Recolectores conservativos vs precisos
2357 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2359 Los recolectores *conservativos* son aquellos que tienen la capacidad de poder
2360 lidiar con un *root set* o celdas que no tengan información de tipos asociada.
2361 Esto significa que el recolector no sabe donde hay punteros (o referencias) en
2362 una celda o raiz dada. Es decir, una ubicación particular puede ser un puntero
2363 o no. Esto trae una variada cantidad de problemas, como retención de celdas
2364 que en realidad son *basura* simplemente porque hay algún dato que coincide
2365 con la dirección de memoria en la que está almacenada esa celda *basura*
2366 [#gcflasepos]_. Además los recolectores puramente conservativos no puede mover
2367 celdas (ver :ref:`gc_moving`), porque no pueden arriesgarse a actualizar los
2368 punteros por el riesgo que existe de que sean falsos positivos.
2370 .. [#gcflasepos] Esto es lo que se conoce como un *falso positivo*, algo que
2371 aparenta ser un puntero pero en realidad no lo es.
2373 Sin embargo hay ciertas circunstancias que hacen que no quede más remedio que
2374 el recolector sea conservativo, por ejemplo cuando se utiliza un recolector de
2375 basura para un lenguaje que no ha sido pensado para tener uno (como C o C++).
2377 Por el contrario, los recolectores que poseen a su disposición información
2378 completa sobre el tipo de la celda, y por ende información sobre cuales de sus
2379 campos son realmente punteros, son denominados *precisos*. Estos recolectores
2380 no están sujetos a estas limitaciones y por lo tanto son potencialmente más
2381 eficientes en cuanto a tiempo y espacio. Los lenguajes que fueron diseñados
2382 para tener un recolector de basura (y en especial aquellos que son de relativo
2383 alto nivel) en general disponen de recolectores precisos.
2385 Hay casos donde se posee información de tipos para algunas celdas solamente,
2386 o más comunmente se posee información de tipos de celdas que se encuentran en
2387 el *heap* pero no para el *stack* y registros (por ejemplo [MOLA06]_). En
2388 estos casos se puede adoptar un esquema híbrido y tratar algunas referencias
2389 de forma conservativa y otras de forma precisa, de manera de mitigar, aunque
2390 sea de forma parcial, los efectos adversos de los recolectores conservativos.
2391 Estos recolectores son conocidos como *semi-precisos*. Los recolectores
2392 semi-precisos pueden mover celdas si utilizan un mecanismo de *pinning* (ver
2395 El ejemplo de recolector conservativo por excelencia es el recolector
2396 `Boehm-Demers-Wiser`_ ([BOEH88]_, [BOEH91]_, [BOEH93]_, [BOEHWD]_) aunque
2397 puede comportarse de forma semi-precisa si el usuario se encarga de darle la
2398 información de tipos (en cuyo caso el recolector deja de ser transparente para
2399 el usuario). Otros ejemplos de recolectores con cierto grado de
2400 conservativismo son el :ref:`recolector actual de D <dgc_actual>` y [BLAC08]_.
2406 Recolección particionada
2407 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2409 Otra forma de reducir la cantidad de pausas y la cantidad de trabajo realizado
2410 por el recolector en general es particionando el *heap* de manera tal de
2411 recolectar solo las partes donde más probabilidad de encontrar *basura* haya.
2413 Entonces, si el recolector tiene algún mecanismo para identificar zonas de
2414 alta concentración de *basura* puede hacer la recolección solo en ese área
2415 donde el trabajo va a ser mejor recompensado (ver :vref:`fig:gc-part`).
2417 .. fig:: fig:gc-part
2419 Concentración de basura en distintas particiones del *heap*.
2424 _______________________________________________________________________
2426 | +-----------------------------+ +-----------------------------+ |
2427 | / Baja \ / Alta \ |
2429 | GGGGGGGZZGGGGGZZGGGGGGGGZZGGGGGGGGZZZZZGGZZZZZZZZZZZZZZZZGGZZZZZZGGZZ |
2430 | GGGGGGGZZGGGGGZZGGGGGGGGZZGGGGGGGGZZZZZGGZZZZZZZZZZZZZZZZGGZZZZZZGGZZ |
2432 | GG Celdas vivas ZZ Basura |
2433 |_______________________________________________________________________|
2436 Sin embargo encontrar zonas de alta concentración no es trivial. La forma más
2437 divulgada de encontrar estas zonas es particionando el *heap* en un área
2438 utilizada para almacenar celdas *jóvenes* y otra para celdas *viejas*. Una
2439 celda *vieja* es aquella que ha *sobrevivido* una cantidad N de recolecciones,
2440 mientras que el resto se consideran *jóvenes* (las celdas *nacen* jóvenes).
2441 Los recolectores que utilizan este tipo de partición son ampliamente conocido
2442 como recolectores **generacionales**. La *hipótesis generacional* dice que el
2443 área de celdas jóvenes tiene una mayor probabilidad de ser un área de alta
2444 concentración de basura [JOLI96]_. Basandose en esto, los recolectores
2445 generacionales primero intentan recuperar espacio del área de celdas jóvenes
2446 y luego, de ser necesario, del área de celdas viejas. Es posible tener varias
2447 generaciones e ir subiendo de generación a generación a medida que es
2448 necesario. Sin embargo en general no se obtienen buenos resultados una vez que
2449 se superan las 3 particiones. La complejidad que trae este método es que para
2450 recolectar la generación joven es necesario tomar las referencias de la
2451 generación vieja a la joven como parte del *root set* (de otra forma podrían
2452 tomarse celdas como *basura* que todavía son utilizadas por las celdas
2453 viejas). Revisar toda la generación vieja no es una opción porque sería
2454 prácticamente lo mismo que realizar una recolección del *heap* completo. La
2455 solución está entonces, una vez más, en instrumentar el *mutator* para que
2456 avise al recolector cuando cambia una referencia de la generación vieja a la
2457 joven (no es necesario monitorear las referencias en sentido inverso ya que
2458 cuando se recolecta la generación vieja se hace una recolección del *heap*
2461 Sin embargo, a pesar de ser este el esquema más difundido para particionar el
2462 *heap* y realizar una recolección parcial sobre un área de alta concentración
2463 de basura no es la única. Otros recolectores proponen hacer un análisis
2464 estático del código revisando la conectividad entre los objetos según sus
2465 tipos (esto es posible solo en lenguajes con tipado estático), de manera tal
2466 de separar en distintas áreas grupos de tipos que no pueden tener referencias
2467 entre sí [HIRZ03]_. Este análisis hace que sea inecesario instrumentar el
2468 *mutator* para reportar al recolector cambios de referencias
2469 inter-particiones, sencillamente porque queda demostrado que no existe dicho
2470 tipo de referencias. Esto quita una de las principale ineficiencias
2471 y complejidades del esquema generacional.
2475 .. include:: links.rst
2477 .. vim: set ts=3 sts=3 sw=3 et tw=78 :