example - garbage collector java
¿Cómo funciona la recolección de basura de Java con referencias circulares? (8)
Bill respondió tu pregunta directamente. Como dijo Amnon, tu definición de recolección de basura es solo el conteo de referencias. Solo quería agregar que incluso algoritmos muy simples como la marca y el barrido y la colección de copias manejan fácilmente referencias circulares. Entonces, ¡nada mágico al respecto!
Según entiendo, la recolección de basura en Java limpia algún objeto si nada más está ''apuntando'' a ese objeto.
Mi pregunta es, ¿qué pasa si tenemos algo como esto?
class Node {
public object value;
public Node next;
public Node(object o, Node n) { value = 0; next = n;}
}
//...some code
{
Node a = new Node("a", null),
b = new Node("b", a),
c = new Node("c", b);
a.next = c;
} //end of scope
//...other code
a
, b
y c
deberían ser basura, pero todos están siendo referenciados por otros objetos.
¿Cómo lidia la recolección de basura de Java con esto? (¿o es simplemente un drenaje de memoria?)
El GC de Java considera que los objetos son "basura" si no son accesibles a través de una cadena que comienza en una raíz de recolección de basura, por lo que estos objetos serán recolectados. Aunque los objetos pueden apuntarse entre sí para formar un ciclo, siguen siendo basura si están aislados de la raíz.
Consulte la sección sobre objetos inalcanzables en el Apéndice A: La verdad sobre la recolección de basura en el rendimiento de la plataforma Java: estrategias y tácticas para los detalles sangrientos.
Estás en lo correcto. La forma específica de recolección de basura que describes se llama " recuento de referencias ". La forma en que funciona (conceptualmente, al menos, la mayoría de las implementaciones modernas de conteo de referencias se implementan de manera bastante diferente) en el caso más simple, se ve así:
- cada vez que se agrega una referencia a un objeto (por ejemplo, se asigna a una variable o un campo, se pasa al método, etc.), su recuento de referencias se incrementa en 1
- siempre que se elimine una referencia a un objeto (el método devuelve, la variable sale del alcance, el campo se vuelve a asignar a un objeto diferente o el objeto que contiene el campo se recoge la basura), el recuento de referencias se reduce en 1
- tan pronto como el recuento de referencias llegue a 0, ya no hay más referencia al objeto, lo que significa que ya nadie puede usarlo, por lo tanto, es basura y se puede recoger
Y esta simple estrategia tiene exactamente el problema que usted describe: si las referencias A y B hacen referencia a A, entonces sus dos recuentos de referencia nunca pueden ser inferiores a 1, lo que significa que nunca se recopilarán.
Hay cuatro formas de lidiar con este problema:
- Ignoralo. Si tiene suficiente memoria, sus ciclos son pequeños e infrecuentes y su tiempo de ejecución es corto, tal vez pueda salirse con la suya simplemente sin recolectar ciclos. Piense en un intérprete de script de shell: los scripts de shell normalmente solo se ejecutan durante unos segundos y no asignan mucha memoria.
- Combine su recolector de basura de conteo de referencia con otro recolector de basura que no tenga problemas con los ciclos. CPython hace esto, por ejemplo: el colector de basura principal en CPython es un colector de recuento de referencia, pero de vez en cuando se ejecuta un recolector de basura de seguimiento para recopilar los ciclos.
- Detecta los ciclos. Desafortunadamente, detectar ciclos en un gráfico es una operación bastante costosa. En particular, requiere más o menos la misma sobrecarga que un recolector de búsqueda, por lo que podría usar uno de esos.
- No implemente el algoritmo de la manera ingenua que usted y yo haríamos: desde la década de 1970, se han desarrollado múltiples algoritmos bastante interesantes que combinan la detección de ciclos y el recuento de referencias en una sola operación de una manera inteligente que es significativamente más barata que hacerlo. ambos por separado o haciendo un coleccionista de rastreo.
Por cierto, la otra forma importante de implementar un recolector de basura (y ya lo insinué un par de veces más arriba), es el rastreo . Un colector de rastreo se basa en el concepto de accesibilidad . Empiezas con un conjunto raíz que sabes que es siempre alcanzable (constantes globales, por ejemplo, o la clase Object
, el alcance léxico actual, el marco de pila actual) y desde allí trazas todos los objetos accesibles desde el conjunto raíz. luego, todos los objetos accesibles desde los objetos accesibles desde el conjunto raíz, y así sucesivamente, hasta que tenga el cierre transitivo. Todo lo que no está en ese cierre es basura.
Como un ciclo solo es accesible dentro de sí mismo, pero no se puede acceder desde el conjunto raíz, se recopilará.
La recolección de basura por lo general no significa "limpiar algún objeto si nada más está ''apuntando'' a ese objeto" (eso es contar de referencia). La recolección de basura significa aproximadamente encontrar objetos que no se pueden alcanzar desde el programa.
Por lo tanto, en su ejemplo, después de que a, byc se salgan del alcance, el GC puede recopilarlos, ya que ya no puede acceder a estos objetos.
Los GC de Java en realidad no se comportan como usted describe. Es más exacto decir que parten de un conjunto base de objetos, frecuentemente llamados "raíces GC", y recopilan cualquier objeto que no se puede alcanzar desde una raíz.
Las raíces de GC incluyen cosas como:
- variables estáticas
- variables locales (incluidas todas las referencias ''this'' aplicables) actualmente en la pila de un hilo en ejecución
Entonces, en su caso, una vez que las variables locales a, b y c salen del alcance al final de su método, no hay más raíces de GC que contengan, directa o indirectamente, una referencia a cualquiera de sus tres nodos, y serán elegibles para la recolección de basura.
El enlace de TofuBeer tiene más detalles si lo desea.
Un recolector de basura comienza desde un conjunto "raíz" de lugares que siempre se consideran "alcanzables", como los registros de la CPU, la pila y las variables globales. Funciona al encontrar punteros en esas áreas y encontrar recursivamente todo lo que señalan. Una vez que se encuentra todo eso, todo lo demás es basura.
Hay, por supuesto, bastantes variaciones, principalmente por el bien de la velocidad. Por ejemplo, la mayoría de los recolectores de basura modernos son "generacionales", lo que significa que dividen los objetos en generaciones y, a medida que un objeto envejece, el recolector de basura va más y más tiempo entre intentos de averiguar si ese objeto sigue siendo válido o no. - Simplemente comienza a suponer que si ha vivido mucho tiempo, las posibilidades son bastante buenas de que continúe viviendo por más tiempo.
No obstante, la idea básica sigue siendo la misma: todo se basa en comenzar a partir de un conjunto de raíces de cosas que da por sentado que aún se pueden usar, y luego buscar todos los indicadores para encontrar qué más podría estar en uso.
Interesante a un lado: es posible que las personas se sorprendan a menudo por el grado de similitud entre esta parte de un recolector de basura y el código para ordenar objetos para cosas como llamadas a procedimientos remotos. En cada caso, está comenzando desde un conjunto de objetos raíz y persiguiendo punteros para encontrar todos los otros objetos a los que se refieren ...
sí, ¡el recolector de basura de Java maneja la referencia circular!
How?
Hay objetos especiales llamados raíces de recolección de basura (raíces de GC). Estos son siempre alcanzables y también cualquier objeto que los tenga en su propia raíz.
Una aplicación Java simple tiene las siguientes raíces de GC:
- Variables locales en el método principal
- El hilo principal
- Variables estáticas de la clase principal
Para determinar qué objetos ya no se usan, la JVM ejecuta de forma intermitente lo que se llama acertadamente un algoritmo de marca y barrido . Funciona de la siguiente manera
- El algoritmo atraviesa todas las referencias de objeto, comenzando con las raíces de GC, y marca cada objeto encontrado como vivo.
- Toda la memoria del montón que no está ocupada por objetos marcados se recupera. Simplemente se marca como libre, esencialmente barrido sin objetos no utilizados.
Por lo tanto, si un objeto no es accesible desde las raíces de la GC (incluso si está referenciado por sí mismo o por referencia cíclica), estará sujeto a la recolección de basura.
Por supuesto, a veces esto puede provocar la pérdida de memoria si el programador se olvida de eliminar un objeto.
Fuente: Gestión de memoria de Java
Este artículo (ya no está disponible) profundiza sobre el recolector de basura (conceptualmente ... hay varias implementaciones). La parte relevante de su publicación es "A.3.4 inalcanzable":
A.3.4 Inaccesible Un objeto ingresa un estado inalcanzable cuando no existen más referencias fuertes a él. Cuando un objeto es inalcanzable, es un candidato para la recolección. Tenga en cuenta la fraseología: el hecho de que un objeto sea un candidato para la recopilación no significa que se recopilará inmediatamente. La JVM puede demorar la recopilación hasta que haya una necesidad inmediata de que el objeto consuma la memoria.