stringtranslate.com

Seguimiento de la recolección de basura

En programación informática , el seguimiento de la recolección de basura es una forma de gestión automática de la memoria que consiste en determinar qué objetos deben desasignarse ("recolección de basura") rastreando qué objetos son accesibles mediante una cadena de referencias de ciertos objetos "raíz", y considerando la descansar como "basura" y recogerlos. El rastreo es el tipo más común de recolección de basura (hasta tal punto que "recolección de basura" a menudo se refiere al método de rastreo, en lugar de otros como el recuento de referencias ) y hay una gran cantidad de algoritmos utilizados en la implementación.

Accesibilidad de un objeto

De manera informal, un objeto es accesible si al menos una variable en el programa hace referencia a él, ya sea directamente o mediante referencias de otros objetos accesibles. Más precisamente, sólo se puede acceder a los objetos de dos maneras:

  1. Un conjunto distinguido de raíces: objetos que se supone son alcanzables. Normalmente, estos incluyen todos los objetos a los que se hace referencia desde cualquier lugar de la pila de llamadas (es decir, todas las variables y parámetros locales en las funciones que se están invocando actualmente) y cualquier variable global .
  2. Cualquier cosa a la que se haga referencia desde un objeto accesible es en sí misma accesible; Más formalmente, la accesibilidad es un cierre transitivo .

La definición de accesibilidad de "basura" no es óptima, en la medida en que la última vez que un programa usa un objeto podría ser mucho antes de que ese objeto caiga fuera del alcance del entorno. A veces se hace una distinción entre basura sintáctica , aquellos objetos que el programa no puede alcanzar, y basura semántica , aquellos objetos que de hecho nunca volverá a utilizar. Por ejemplo:

Objeto x = nuevo Foo (); Objeto y = nueva barra (); x = nuevo Quux (); /* En este punto, sabemos que nunca se accederá al objeto Foo * originalmente asignado a x: es basura sintáctica. */           /* En el siguiente bloque, y *podría* ser basura semántica; * pero no lo sabremos hasta que x.check_something() devuelva * algún valor, si es que devuelve algún valor. */ if ( x . comprobar_algo ()) { x . hacer_algo ( y ); } Sistema . salir ( 0 );   

Se puede demostrar fácilmente que el problema de identificar con precisión la basura semántica es parcialmente decidible : un programa que asigna un objeto X , ejecuta un programa de entrada arbitrario P y usa X si y sólo si P termina requeriría un recolector de basura semántica para resolver la detención. problema . Aunque los métodos heurísticos conservadores para la detección de basura semántica siguen siendo un área de investigación activa, esencialmente todos los recolectores de basura prácticos se centran en la basura sintáctica. [ cita necesaria ]

Otra complicación de este enfoque es que, en lenguajes con tipos de referencia y tipos de valores sin caja , el recolector de basura necesita de alguna manera poder distinguir qué variables de la pila o campos de un objeto son valores regulares y cuáles son referencias: en la memoria, un número entero y una referencia pueden parecerse. Luego, el recolector de basura necesita saber si debe tratar el elemento como una referencia y seguirlo, o si es un valor primitivo. Una solución común es el uso de punteros etiquetados .

Referencias fuertes y débiles.

El recolector de basura solo puede recuperar objetos que no tengan referencias que los apunten directa o indirectamente desde el conjunto raíz. Sin embargo, algunos programas requieren referencias débiles , que deberían poder utilizarse mientras el objeto exista, pero no deberían prolongar su vida útil. En las discusiones sobre referencias débiles, las referencias ordinarias a veces se denominan referencias fuertes . Un objeto es elegible para la recolección de basura si no hay referencias fuertes (es decir, ordinarias) a él, aunque todavía pueda haber algunas referencias débiles.

Una referencia débil no es simplemente un puntero cualquiera al objeto que no le importa al recolector de basura. El término generalmente se reserva para una categoría adecuadamente administrada de objetos de referencia especiales que son seguros de usar incluso después de que el objeto desaparece porque pasan a un valor seguro (generalmente null). Una referencia insegura que el recolector de basura no conoce simplemente permanecerá suspendida al continuar haciendo referencia a la dirección donde residía anteriormente el objeto. Esta no es una referencia débil.

En algunas implementaciones, las referencias débiles se dividen en subcategorías. Por ejemplo, la máquina virtual Java proporciona tres formas de referencias débiles, a saber, referencias suaves , [1] referencias fantasmas , [2] y referencias débiles regulares. [3] Un objeto con referencia suave solo es elegible para recuperación si el recolector de basura decide que el programa tiene poca memoria. A diferencia de una referencia suave o una referencia débil normal, una referencia fantasma no proporciona acceso al objeto al que hace referencia. En cambio, una referencia fantasma es un mecanismo que permite al recolector de basura notificar al programa cuando el objeto al que se hace referencia se vuelve accesible de forma fantasma . Un objeto es accesible de forma fantasma si todavía reside en la memoria y está referenciado por una referencia fantasma, pero su finalizador ya se ha ejecutado. De manera similar, Microsoft.NET proporciona dos subcategorías de referencias débiles, [4] a saber, referencias débiles largas (pistas de resurrección) y referencias débiles cortas.

Colecciones débiles

También se pueden diseñar estructuras de datos que tengan características de seguimiento débiles. Por ejemplo, las tablas hash débiles son útiles. Al igual que una tabla hash normal, una tabla hash débil mantiene una asociación entre pares de objetos, donde cada par se entiende como una clave y un valor. Sin embargo, la tabla hash en realidad no mantiene una referencia sólida sobre estos objetos. Se produce un comportamiento especial cuando la clave, el valor o ambos se convierten en basura: la entrada de la tabla hash se elimina espontáneamente. Existen mejoras adicionales, como tablas hash que solo tienen claves débiles (las referencias de valores son referencias fuertes y ordinarias) o solo valores débiles (las referencias de claves son fuertes).

Las tablas hash débiles son importantes para mantener asociaciones entre objetos, de modo que los objetos involucrados en la asociación aún pueden convertirse en basura si ya no hay nada en el programa que haga referencia a ellos (aparte de la tabla hash de asociación).

El uso de una tabla hash normal para tal fin podría provocar una "pérdida de memoria lógica": la acumulación de datos accesibles que el programa no necesita ni utilizará.

Algoritmo básico

Los recolectores de rastreo se llaman así porque rastrean el conjunto de memoria de trabajo. Estos recolectores de basura realizan la recolección en ciclos. Es común que los ciclos se activen cuando no hay suficiente memoria libre para que el administrador de memoria satisfaga una solicitud de asignación. Pero a menudo el mutador puede solicitar los ciclos directamente o ejecutarlos según un cronograma. El método original implica una ingenua marca y barrido en la que se toca todo el conjunto de recuerdos varias veces.

Marcar y barrer ingenuo

"Marcar y barrer ingenuo en acción en un montón que contiene ocho objetos" . Las flechas representan referencias a objetos . Los círculos representan los objetos mismos. Los objetos n.° 1, n.° 2, n.° 3, n.° 4 y n.° 6 tienen fuertes referencias desde el conjunto raíz. Por otro lado, los objetos #5, #7 y #8 no están fuertemente referenciados ni directa ni indirectamente desde el conjunto raíz; por lo tanto, son basura.

En el método ingenuo de marcar y barrer, cada objeto en la memoria tiene un indicador (normalmente un solo bit) reservado para uso exclusivo de recolección de basura. Esta bandera siempre se borra , excepto durante el ciclo de recolección.

La primera etapa es la etapa de marca , que recorre el árbol de todo el "conjunto de raíces" y marca cada objeto al que apunta una raíz como "en uso". Todos los objetos a los que apuntan esos objetos, etc., también se marcan, de modo que se marca cada objeto al que se puede acceder a través del conjunto raíz.

En la segunda etapa, la etapa de barrido , se escanea toda la memoria de principio a fin, examinando todos los bloques libres o usados; aquellos que no están marcados como "en uso" no son accesibles por ninguna raíz y su memoria se libera. Para los objetos que fueron marcados en uso, la bandera de uso se borra, preparándose para el siguiente ciclo.

Este método tiene varias desventajas, la más notable es que todo el sistema debe suspenderse durante la recolección; no se puede permitir ninguna mutación del conjunto de trabajo. Esto puede hacer que los programas se "congelan" periódicamente (y generalmente de forma impredecible), haciendo imposibles algunas aplicaciones en tiempo real y en las que el tiempo es crítico. Además, se debe examinar toda la memoria de trabajo, gran parte de ella dos veces, lo que podría causar problemas en los sistemas de memoria paginada .

Marcado tricolor

Un ejemplo de marcado tricolor en un montón con 8 objetos. Los objetos blancos, grises y negros están representados por gris claro, amarillo y azul, respectivamente.

Debido a estos problemas de rendimiento, la mayoría de los recolectores de basura de rastreo modernos implementan alguna variante de la abstracción de marcado tricolor , pero los recolectores simples (como el recolector de marca y barrido ) a menudo no hacen explícita esta abstracción. El marcado tricolor funciona como se describe a continuación.

Se crean tres conjuntos: blanco , negro y gris :

En muchos algoritmos, inicialmente el conjunto negro comienza vacío, el conjunto gris es el conjunto de objetos a los que se hace referencia directamente desde las raíces y el conjunto blanco incluye todos los demás objetos. Cada objeto en la memoria está en todo momento exactamente en uno de los tres conjuntos. El algoritmo procede de la siguiente manera:

  1. Elija un objeto o del conjunto gris
  2. Mueve cada objeto blanco o referencia al conjunto gris. Esto garantiza que ni este objeto ni ningún objeto al que haga referencia puedan ser recolectados como basura.
  3. Mover al conjunto negro
  4. Repita los últimos tres pasos hasta que el conjunto gris esté vacío.

Cuando el conjunto gris está vacío, el escaneo se completa; los objetos negros son accesibles desde las raíces, mientras que los objetos blancos no lo son y pueden recolectarse como basura.

Dado que todos los objetos a los que no se puede acceder inmediatamente desde las raíces se agregan al conjunto de blancos, y los objetos solo pueden moverse de blanco a gris y de gris a negro, el algoritmo conserva una invariante importante: ningún objeto negro hace referencia a objetos blancos. Esto asegura que los objetos blancos se puedan liberar una vez que el conjunto gris esté vacío. Esto se llama invariante tricolor . Algunas variaciones del algoritmo no conservan esta invariante sino que utilizan una forma modificada para la cual se mantienen todas las propiedades importantes.

El método tricolor tiene una ventaja importante: se puede realizar "sobre la marcha", sin detener el sistema durante períodos de tiempo significativos. Esto se logra marcando objetos a medida que se asignan y durante la mutación, manteniendo los distintos conjuntos. Al monitorear el tamaño de los conjuntos, el sistema puede realizar la recolección de basura periódicamente, en lugar de según sea necesario. Además, se evita la necesidad de tocar todo el conjunto de trabajo en cada ciclo.

Estrategias de implementación

Móvil versus inmóvil

Una vez que se ha determinado el conjunto inalcanzable, el recolector de basura puede simplemente liberar los objetos inalcanzables y dejar todo lo demás como está, o puede copiar algunos o todos los objetos alcanzables en una nueva área de memoria, actualizando todas las referencias a esos objetos como necesario. Estos se denominan recolectores de basura "inmóviles" y "móviles" (o, alternativamente, "no compactadores" y "compactantes"), respectivamente.

Al principio, un algoritmo en movimiento puede parecer ineficiente en comparación con uno que no se mueve, ya que parecería que se requiere mucho más trabajo en cada ciclo. Pero el algoritmo en movimiento genera varias ventajas de rendimiento, tanto durante el ciclo de recolección de basura como durante la ejecución del programa:

Una desventaja de un recolector de basura móvil es que solo permite el acceso a través de referencias administradas por el entorno de recolección de basura y no permite la aritmética de punteros . Esto se debe a que cualquier puntero a objetos quedará invalidado si el recolector de basura mueve esos objetos (se convierten en punteros colgantes ). Para la interoperabilidad con el código nativo, el recolector de basura debe copiar el contenido del objeto en una ubicación fuera de la región de memoria recolectada de basura. Un enfoque alternativo es fijar el objeto en la memoria, evitando que el recolector de basura lo mueva y permitiendo que la memoria se comparta directamente con punteros nativos (y posiblemente permitiendo la aritmética de punteros). [5]

Copiar versus marcar y barrer versus marcar y no barrer

Los coleccionistas no sólo difieren en si se mueven o no, sino que también se pueden clasificar según cómo tratan los conjuntos de objetos blancos, grises y negros durante un ciclo de recolección.

El enfoque más sencillo es el recopilador semiespacial , que data de 1969. En este recopilador en movimiento, la memoria se divide en "desde el espacio" y "hacia el espacio" del mismo tamaño. Inicialmente, los objetos se asignan "al espacio" hasta que se llena y se activa un ciclo de recolección. Al inicio del ciclo, el "hacia el espacio" se convierte en el "desde el espacio", y viceversa. Los objetos accesibles desde el conjunto raíz se copian del "desde el espacio" al "al espacio". Estos objetos se escanean a su vez y todos los objetos a los que apuntan se copian al "espacio", hasta que todos los objetos accesibles se hayan copiado al "espacio". Una vez que el programa continúa ejecutándose, los nuevos objetos se asignan nuevamente en el "espacio" hasta que vuelve a estar lleno y se repite el proceso.

Este enfoque es muy simple, pero como solo se usa un semiespacio para asignar objetos, el uso de memoria es el doble en comparación con otros algoritmos. La técnica también se conoce como detener y copiar . El algoritmo de Cheney es una mejora del recolector semiespacial.

Un recolector de basura que marca y barre guarda uno o dos bits con cada objeto para registrar si es blanco o negro. El conjunto gris se mantiene como una lista separada o usando otro bit. A medida que se recorre el árbol de referencia durante un ciclo de recopilación (la fase de "marca"), el recopilador manipula estos bits. Un último "barrido" de las áreas de memoria libera los objetos blancos. La estrategia de marcar y barrer tiene la ventaja de que, una vez que se determina el conjunto condenado, se puede seguir una estrategia de recolección móvil o inmóvil. Esta elección de estrategia se puede realizar en tiempo de ejecución, según lo permita la memoria disponible. Tiene la desventaja de "inflar" los objetos en una pequeña cantidad, ya que cada objeto tiene un pequeño costo de memoria oculta debido a la lista/bit extra. Esto puede mitigarse en cierta medida si el recopilador también maneja la asignación, ya que entonces podría utilizar bits no utilizados en las estructuras de datos de asignación. O bien, esta "memoria oculta" se puede eliminar utilizando un puntero Tagged , intercambiando el costo de la memoria por tiempo de CPU. Sin embargo, marcar y barrer es la única estrategia que coopera fácilmente con los asignadores externos en primer lugar.

Un recolector de basura de marcar y no barrer , como el de marcar y barrer, guarda un bit con cada objeto para registrar si es blanco o negro; el conjunto gris se mantiene como una lista separada o usando otro bit. Aquí hay dos diferencias clave. En primer lugar, el blanco y el negro significan cosas diferentes a las que tienen en el coleccionista de marcas y barridos. En un recopilador de "marcar y no barrer", todos los objetos accesibles son siempre negros. Un objeto se marca en negro en el momento de su asignación y permanecerá negro incluso si se vuelve inalcanzable. Un objeto blanco es memoria no utilizada y puede asignarse. En segundo lugar, la interpretación del bit blanco/negro puede cambiar. Inicialmente, el bit blanco/negro puede tener el sentido de (0=blanco, 1=negro). Si una operación de asignación alguna vez no logra encontrar memoria disponible (blanca), eso significa que todos los objetos están marcados como usados ​​(negro). Luego se invierte el sentido del bit blanco/negro (por ejemplo, 0=negro, 1=blanco). Todo se vuelve blanco. Esto rompe momentáneamente la invariante de que los objetos alcanzables son negros, pero inmediatamente sigue una fase de marcado completa para volver a marcarlos en negro. Una vez hecho esto, toda la memoria inalcanzable queda en blanco. No es necesaria ninguna fase de "barrido".

La estrategia de marcar y no barrer requiere la cooperación entre el asignador y el recolector, pero es increíblemente eficiente en términos de espacio ya que solo requiere un bit por puntero asignado (que la mayoría de los algoritmos de asignación requieren de todos modos). Sin embargo, esta ventaja está algo mitigada, ya que la mayoría de las veces grandes porciones de memoria se marcan erróneamente en negro (usada), lo que dificulta devolver recursos al sistema (para que los utilicen otros asignadores, subprocesos o procesos) en tiempos de bajo uso de memoria.

Por lo tanto, la estrategia de marcar y no barrer puede verse como un compromiso entre las ventajas y desventajas de las estrategias de marcar y barrer y las de detener y copiar.

GC generacional (GC efímera)

Se ha observado empíricamente que en muchos programas, los objetos creados más recientemente son también los que tienen más probabilidades de volverse inalcanzables rápidamente (lo que se conoce como mortalidad infantil o hipótesis generacional ). Un GC generacional (también conocido como GC efímero) divide los objetos en generaciones y, en la mayoría de los ciclos, colocará solo los objetos de un subconjunto de generaciones en el conjunto blanco inicial (condenado). Además, el sistema de ejecución mantiene el conocimiento de cuándo las referencias cruzan generaciones observando la creación y sobrescritura de referencias. Cuando se ejecuta el recolector de basura, es posible que pueda utilizar este conocimiento para demostrar que algunos objetos en el conjunto blanco inicial son inalcanzables sin tener que atravesar todo el árbol de referencia. Si la hipótesis generacional se mantiene, esto da como resultado ciclos de recolección mucho más rápidos y al mismo tiempo se recuperan la mayoría de los objetos inalcanzables.

Para implementar este concepto, muchos recolectores de basura generacionales utilizan regiones de memoria separadas para diferentes edades de objetos. Cuando una región se llena, los objetos que contiene se rastrean, utilizando las referencias de las generaciones anteriores como raíces. Esto generalmente da como resultado que la mayoría de los objetos de la generación se recopilen (según la hipótesis), dejándolos para usarlos para asignar nuevos objetos. Cuando una colección no recopila muchos objetos (la hipótesis no se cumple, por ejemplo porque el programa ha calculado una gran colección de objetos nuevos que desea retener), algunos o todos los objetos supervivientes a los que se hace referencia desde la memoria anterior Las regiones se promueven a la siguiente región más alta y luego se puede sobrescribir toda la región con objetos nuevos. Esta técnica permite una recolección de basura incremental muy rápida, ya que normalmente todo lo que se requiere es la recolección de basura de una sola región a la vez.

El carroñero de generación clásica de Ungar tiene dos generaciones. Divide a la generación más joven, llamada "nuevo espacio", en un gran "edén" en el que se crean nuevos objetos y dos "espacios de supervivientes" más pequeños, el espacio de supervivientes del pasado y el espacio de supervivientes del futuro. Los objetos de la generación anterior que pueden hacer referencia a objetos en el nuevo espacio se mantienen en un "conjunto recordado". En cada búsqueda, los objetos en el nuevo espacio se rastrean desde las raíces en el conjunto recordado y se copian en el futuro espacio de supervivientes. Si el espacio de los futuros supervivientes se llena, los objetos que no encajan son promovidos al espacio antiguo, un proceso llamado "tenencia". Al final de la búsqueda, algunos objetos residen en el espacio de supervivientes futuros, y el Edén y el espacio de supervivientes pasados ​​están vacíos. Luego se intercambian el espacio de supervivientes futuros y el espacio de supervivientes pasados ​​y el programa continúa, asignando objetos en el Edén. En el sistema original de Ungar, el Edén es 5 veces más grande que cada espacio de superviviente.

La recolección de basura generacional es un enfoque heurístico y es posible que algunos objetos inalcanzables no se recuperen en cada ciclo. Por lo tanto, en ocasiones puede ser necesario realizar una marca y un barrido completo o copiar la recolección de basura para recuperar todo el espacio disponible. De hecho, los sistemas de ejecución para lenguajes de programación modernos (como Java y .NET Framework ) suelen utilizar algún híbrido de las diversas estrategias que se han descrito hasta ahora; por ejemplo, la mayoría de los ciclos de recopilación pueden abarcar solo unas pocas generaciones, mientras que ocasionalmente se realiza una marca y barrido, y aún más raramente se realiza una copia completa para combatir la fragmentación. Los términos "ciclo menor" y "ciclo mayor" se utilizan a veces para describir estos diferentes niveles de agresión de los coleccionistas.

Detener el mundo versus incremental versus concurrente

Los recolectores de basura simples que detienen el mundo detienen completamente la ejecución del programa para ejecutar un ciclo de recolección, garantizando así que no se asignen nuevos objetos y que los objetos no se vuelvan inaccesibles repentinamente mientras el recolector se está ejecutando.

Esto tiene la desventaja de que el programa no puede realizar ningún trabajo útil mientras se ejecuta un ciclo de recolección (a veces llamado "pausa embarazosa" [6] ). Por lo tanto, la recolección de basura de Stop-the-World es adecuada principalmente para programas no interactivos. Su ventaja es que es más sencillo de implementar y más rápido que la recolección de basura incremental.

Los recolectores de basura incrementales y concurrentes están diseñados para reducir esta interrupción intercalando su trabajo con la actividad del programa principal. Los recolectores de basura incrementales realizan el ciclo de recolección de basura en fases discretas, y se permite la ejecución del programa entre cada fase (y, a veces, durante algunas fases). Los recolectores de basura concurrentes no detienen la ejecución del programa en absoluto, excepto quizás brevemente cuando se escanea la pila de ejecución del programa. Sin embargo, la suma de las fases incrementales tarda más en completarse que un paso de recolección de basura por lotes, por lo que estos recolectores de basura pueden generar un rendimiento total menor.

Es necesario un diseño cuidadoso con estas técnicas para garantizar que el programa principal no interfiera con el recolector de basura y viceversa; por ejemplo, cuando el programa necesita asignar un nuevo objeto, es posible que el sistema de ejecución deba suspenderlo hasta que se complete el ciclo de recolección o notificar de alguna manera al recolector de basura que existe un objeto nuevo y accesible.

Punteros internos y precisos frente a conservadores

Algunos recolectores pueden identificar correctamente todos los punteros (referencias) en un objeto; a estos se les llama coleccionistas precisos (también exactos o exactos ), siendo lo contrario un coleccionista conservador o parcialmente conservador . Los coleccionistas conservadores suponen que cualquier patrón de bits en la memoria podría ser un puntero si, interpretado como un puntero, apuntara a un objeto asignado. Los recolectores conservadores pueden producir falsos positivos, donde la memoria no utilizada no se libera debido a una identificación incorrecta del puntero. Esto no siempre es un problema en la práctica, a menos que el programa maneje una gran cantidad de datos que fácilmente podrían identificarse erróneamente como un puntero. Los falsos positivos generalmente son menos problemáticos en sistemas de 64 bits que en sistemas de 32 bits porque el rango de direcciones de memoria válidas tiende a ser una pequeña fracción del rango de valores de 64 bits. Por tanto, es poco probable que un patrón arbitrario de 64 bits imite un puntero válido. También puede ocurrir un falso negativo si los punteros están "ocultos", por ejemplo usando una lista enlazada XOR . La viabilidad de un recopilador preciso suele depender de las propiedades de seguridad de tipos del lenguaje de programación en cuestión. Un ejemplo para el cual se necesitaría un recolector de basura conservador es el lenguaje C , que permite convertir punteros escritos (no vacíos) en punteros no escritos (vacíos) y viceversa.

Un problema relacionado tiene que ver con los punteros internos o punteros a campos dentro de un objeto. Si la semántica de un lenguaje permite punteros internos, entonces puede haber muchas direcciones diferentes que pueden hacer referencia a partes del mismo objeto, lo que complica determinar si un objeto es basura o no. Un ejemplo de esto es el lenguaje C++ , en el que la herencia múltiple puede hacer que los punteros a los objetos base tengan direcciones diferentes. En un programa estrictamente optimizado, es posible que el puntero correspondiente al objeto en sí se haya sobrescrito en su registro, por lo que dichos punteros internos deben escanearse.

Actuación

El rendimiento del rastreo de los recolectores de basura (tanto la latencia como el rendimiento) depende significativamente de la implementación, la carga de trabajo y el entorno. Las implementaciones ingenuas o el uso en entornos con mucha memoria limitada, en particular sistemas integrados, pueden dar como resultado un rendimiento muy pobre en comparación con otros métodos, mientras que las implementaciones sofisticadas y el uso en entornos con amplia memoria pueden dar como resultado un rendimiento excelente. [ cita necesaria ]

En términos de rendimiento, el rastreo por su naturaleza requiere cierta sobrecarga de tiempo de ejecución implícita , aunque en algunos casos el costo amortizado puede ser extremadamente bajo, en algunos casos incluso inferior a una instrucción por asignación o colección, superando la asignación de pila. [7] La ​​gestión manual de la memoria requiere una sobrecarga debido a la liberación explícita de memoria, y el conteo de referencias tiene una sobrecarga al incrementar y disminuir los conteos de referencia, y verificar si el conteo se ha desbordado o ha caído a cero. [ cita necesaria ]

En términos de latencia, los simples recolectores de basura que detienen el mundo pausan la ejecución del programa para la recolección de basura, lo que puede ocurrir en momentos arbitrarios y tomar un tiempo arbitrariamente largo, lo que los hace inutilizables para la computación en tiempo real , en particular los sistemas integrados, y no son adecuados para aplicaciones interactivas. uso, o cualquier otra situación donde la baja latencia sea una prioridad. Sin embargo, los recolectores de basura incrementales pueden brindar garantías estrictas en tiempo real y, en sistemas con tiempos de inactividad frecuentes y suficiente memoria libre, como computadoras personales, la recolección de basura se puede programar para tiempos de inactividad y tener un impacto mínimo en el rendimiento interactivo. La gestión manual de la memoria (como en C++) y el recuento de referencias tienen un problema similar de pausas arbitrariamente largas en caso de desasignar una estructura de datos grande y todos sus hijos, aunque esto sólo ocurre en momentos fijos, sin depender de la recolección de basura. [ cita necesaria ]

Asignación manual de montón
Recolección de basura

Es difícil comparar los dos casos directamente, ya que su comportamiento depende de la situación. Por ejemplo, en el mejor de los casos para un sistema de recolección de basura, la asignación simplemente incrementa un puntero, pero en el mejor de los casos para la asignación manual del montón, el asignador mantiene listas libres de tamaños específicos y la asignación solo requiere seguir un puntero. Sin embargo, esta segregación de tamaño suele provocar un alto grado de fragmentación externa, lo que puede tener un impacto adverso en el comportamiento de la caché. La asignación de memoria en un lenguaje de recolección de basura se puede implementar mediante la asignación de montón detrás de escena (en lugar de simplemente incrementar un puntero), por lo que las ventajas de rendimiento enumeradas anteriormente no necesariamente se aplican en este caso. En algunas situaciones, sobre todo en los sistemas integrados , es posible evitar tanto la recolección de basura como la sobrecarga de administración del montón mediante la preasignación de grupos de memoria y el uso de un esquema liviano y personalizado para la asignación/desasignación. [8]

Es más probable que la sobrecarga de las barreras de escritura se note en un programa de estilo imperativo que frecuentemente escribe punteros en estructuras de datos existentes que en un programa de estilo funcional que construye datos sólo una vez y nunca los cambia.

Algunos avances en la recolección de basura pueden entenderse como reacciones a problemas de rendimiento. Los primeros coleccionistas eran coleccionistas que detenían el mundo, pero el rendimiento de este enfoque distraía en las aplicaciones interactivas. La recaudación incremental evitó esta interrupción, pero a costa de una menor eficiencia debido a la necesidad de barreras. Las técnicas de recopilación generacional se utilizan con recopiladores incrementales y de parada del mundo para aumentar el rendimiento; la desventaja es que parte de la basura no se detecta como tal durante más tiempo del normal.

Determinismo

Recolección de basura en tiempo real

Si bien la recolección de basura generalmente no es determinista, es posible utilizarla en sistemas estrictos en tiempo real . Un recolector de basura en tiempo real debe garantizar que incluso en el peor de los casos dedicará una cierta cantidad de recursos computacionales a los subprocesos mutadores. Las restricciones impuestas a un recolector de basura en tiempo real suelen estar basadas en el trabajo o en el tiempo. Una restricción basada en el tiempo sería así: dentro de cada ventana de tiempo de duración T , se debe permitir que los subprocesos mutadores se ejecuten al menos durante Tm tiempo. Para el análisis basado en el trabajo, MMU (utilización mínima de mutador) [9] generalmente se usa como restricción en tiempo real para el algoritmo de recolección de basura.

Una de las primeras implementaciones de recolección de basura en tiempo real para JVM se basó en el algoritmo Metronome, [10] cuya implementación comercial está disponible como parte de IBM WebSphere Real Time . [11] Otro algoritmo duro de recolección de basura en tiempo real es Staccato, disponible en la JVM J9 de IBM , que también proporciona escalabilidad a grandes arquitecturas multiprocesador, al tiempo que aporta varias ventajas sobre Metronome y otros algoritmos que, por el contrario, requieren hardware especializado. . [12]

Un desafío importante para la recolección de basura en tiempo real en arquitecturas modernas de múltiples núcleos es diseñar una recolección de basura concurrente sin bloqueo, sin permitir que los subprocesos concurrentes se bloqueen entre sí y creen pausas impredecibles. Un estudio de algoritmos que permiten la recolección de basura concurrente en tiempo real sin bloqueo aparece en un artículo de Pizlo et al. en Investigación de Microsoft. [13]

Ver también

Referencias

  1. ^ "Clase SoftReference<T>". Plataforma Java™ Edición estándar. 7 . Oráculo . Consultado el 25 de mayo de 2013 .
  2. ^ "Clase PhantomReference <T>". Plataforma Java™ Edición estándar. 7 . Oráculo . Consultado el 25 de mayo de 2013 .
  3. ^ "Clase Referencia débil<T>". Plataforma Java™ Edición estándar. 7 . Oráculo . Consultado el 25 de mayo de 2013 .
  4. ^ "Referencias débiles". .NET Marco 4.5 . Microsoft . Consultado el 25 de mayo de 2013 .
  5. ^ "Copiar y fijar". Documentos de Microsoft . Consultado el 25 de abril de 2022 .
  6. ^ Steele, Guy L. (septiembre de 1975). "Recolección de basura compactadora multiprocesamiento". Comunicaciones de la ACM . 18 (9): 495–508. doi : 10.1145/361002.361005 . S2CID  29412244.
  7. ^ Appel, Andrew W. (17 de junio de 1987). "La recolección de basura puede ser más rápida que la asignación de pilas" (PDF) . Cartas de procesamiento de información . 25 (4): 275–279. CiteSeerX 10.1.1.49.2537 . doi :10.1016/0020-0190(87)90175-X. S2CID  2575400 . Consultado el 25 de abril de 2022 . 
  8. ^ Hopwood, David (1 de enero de 2007). "Asignación de memoria en sistemas embebidos". cap-talk (lista de correo). EROS . Archivado desde el original el 24 de septiembre de 2015.
  9. ^ Cheng, Perry; Blelloch, Guy E. (22 de junio de 2001). "Un recolector de basura paralelo en tiempo real" (PDF) . Avisos ACM SIGPLAN . 36 (5): 125-136. doi :10.1145/381694.378823.
  10. ^ Tocino, David F.; Cheng, Perry; Rajan, VT (noviembre de 2003). "El metrónomo: un enfoque más sencillo para la recolección de basura en sistemas en tiempo real" (PDF) . En Corsaro, Angelo; Cytrón, Ron; Santoro, Corrado (eds.). Taller sobre Tecnologías Java para Sistemas Embebidos y en Tiempo Real . JTRES'03. Talleres OTM 2003. En camino hacia sistemas de Internet significativos 2003 . LNCS . vol. 2889, págs. 466–478. CiteSeerX 10.1.1.3.8544 . doi :10.1007/978-3-540-39962-9_52. ISBN  3-540-20494-6. ISSN  0302-9743. S2CID  14565934. Archivado desde el original (PDF) el 26 de octubre de 2006.
  11. ^ Birón, Benjamín; Sciampacone, Ryan (2 de mayo de 2007). "Java en tiempo real, parte 4: recolección de basura en tiempo real". IBM DeveloperWorks . Archivado desde el original el 9 de noviembre de 2020.
  12. ^ McCloskey, Bill; Tocino, David F.; Cheng, Perry; Grove, David (22 de febrero de 2008). Staccato: un recolector de basura compactado en tiempo real paralelo y concurrente para multiprocesadores (PDF) (Reporte técnico). División de Investigación de IBM. RC24504 . Consultado el 25 de abril de 2022 .
  13. ^ Pizlo, Phil; Petrank, Erez ; Steensgaard, Bjarne (junio de 2008). Actas de la 29ª Conferencia ACM SIGPLAN sobre diseño e implementación de lenguajes de programación (PDF) . Conferencia PLDI 2008. págs. 33–44. CiteSeerX 10.1.1.3.8544 . doi :10.1145/1375581.1375587. ISBN  9781595938602.