46

Dada una agregación de instancias de clase que se refieren entre sí de forma compleja, circular: ¿es posible que el recolector de basura no pueda liberar estos objetos?Referencias circulares en Java

Recuerdo vagamente que esto era un problema en la JVM en el pasado, pero creo que esto se resolvió hace años. sin embargo, alguna investigación en jhat ha revelado que una referencia circular es la razón de una fuga de memoria que ahora enfrento.

Nota: Siempre he tenido la impresión de que la JVM era capaz de resolver referencias circulares y liberar esas "islas de basura" de la memoria. Sin embargo, estoy planteando esta pregunta solo para ver si alguien ha encontrado alguna excepción.

Respuesta

40

Solo una implementación muy ingenua tendría un problema con las referencias circulares. Wikipedia tiene un buen article en los diferentes algoritmos de GC. Si realmente desea obtener más información, intente (Amazon) Garbage Collection: Algorithms for Automatic Dynamic Memory Management. Java ha tenido un buen recolector de basura desde 1.2 y uno excepcionalmente bueno en 1.5 y Java 6.

La parte difícil para mejorar el GC es reducir las pausas y la sobrecarga, no cosas básicas como la referencia circular.

21

El recolector de basura sabe dónde están los objetos raíz: estáticos, locales en la pila, etc. y si los objetos no son accesibles desde una raíz, entonces serán recuperados. Si son alcanzables, entonces necesitan quedarse.

0

El recolector de basura es una pieza de software muy sofisticada, ha sido probado en un enorme banco de pruebas JCK. NO es perfecto PERO hay una posibilidad muy buena de que mientras el compilador de java (javac) compile todas tus clases y JVM lo instanciará, entonces deberías estar bien.

Por otra parte, si mantiene referencias a la raíz de este gráfico de objetos, la memoria NO se liberará PERO si sabe lo que está haciendo, debería estar bien.

3

No, al menos utilizando la JVM oficial de Sun, el recolector de basura podrá detectar estos ciclos y liberar la memoria tan pronto como ya no haya referencias desde el exterior.

2

Sólo para amplificar lo que ya se ha dicho:

La aplicación que he estado trabajando durante seis años cambiado recientemente de Java 1.4 para Java 1.6, y hemos descubierto que hemos tenido que añadir estática referencias a cosas que ni siquiera nos dimos cuenta que antes eran basura coleccionable. No necesitamos la referencia estática antes porque el recolector de basura solía chupar, y ahora es mucho mejor.

+0

Si no se pudo alcanzar antes, ¿cómo supo que se fue? Me pregunto si hay algún tipo de enlace en el que no esté pensando que pueda hacer que el sistema lo guarde. Un hilo o un oyente. –

+0

¿Cómo supiste si los objetos se recogieron o no cuando se eliminaron todas las referencias a ellos? ¿Eran referencias débiles o tenía un enlace a través de JNI o ​​era algún tipo de objeto en vivo que se puede recopilar incluso cuando todavía está activo? – Brian

+0

El problema fue que cuando fuimos a referencia, ya no estaba. Olvidé los detalles exactos porque fue hace unos meses, pero creo que lo llamábamos a través de un método de RMI. –

2

Recuento de referencias Los GC son notorios por este problema. En particular, Suns JVM no utiliza un GC de recuento de referencias.

Si no se puede acceder al objeto desde la raíz del montón (generalmente, como mínimo, a través de los cargadores de clases si no hay nada más0), los objetos se destruirán ya que no se copiarán durante un GC Java típico al nuevo montón.

+0

El recuento de referencias es un muy buen ejemplo de una implementación ingenua. –

+0

En realidad, es una implementación inteligente, también es una implementación bastante simple, y si bien tiene sus limitaciones, es un algoritmo viable, especialmente en entornos con memoria limitada. –

+0

He estado leyendo sobre el desarrollo de MacOs y, aparentemente, ObjectiveC admite tanto la recolección de basura basada en conteos de referencia como un esquema más sofisticado similar al de Java. –

3

la especificación Java dice que el recolector de basura cubo de basura recoger el objeto sólo si es no accesible desde cualquier hilo.

accesible significa que hay una referencia, o la cadena de referencias que conduce desde A a B, y puede ir a través de C, D, ... Z por todo lo que le importa.

La JVM no está coleccionando cosas no ha sido un problema para mí desde 2000, pero su kilometraje puede variar.

Sugerencia: la serialización de Java almacena objetos en caché para hacer que la transferencia de malla de objeto sea eficiente. Si tiene muchos objetos grandes y transitorios, y toda su memoria está siendo trabada, reinicie su serializador para borrar su caché.

4

Si mal no recuerdo, entonces, de acuerdo con las especificaciones, , solo hay garantías sobre lo que la JVM no puede recopilar (todo lo que se puede alcanzar), no lo que recogerá.

A menos que trabaje con JVM en tiempo real, la mayoría de los recolectores de basura modernos deben ser capaces de manejar estructuras de referencia complejas e identificar "subgrafos" que se pueden eliminar de forma segura. La eficacia, la latencia y la probabilidad de hacerlo mejoran con el tiempo a medida que más ideas de investigación se abren paso en VM estándar (en lugar de investigación).

+1

Y para enfatizar el punto, una JVM que asume montones infinitos y nunca recoge basura es totalmente compatible (aunque ineficiente ;-) – ddimitrov

11

Ryan, a juzgar por su comentario al Circular References in Java, cayó en la trampa de hacer referencia a los objetos de una clase, que probablemente fue cargado por el cargador de clases bootstrap/system. Cada clase es referenciada por el cargador de clases que cargó la clase y, por lo tanto, solo puede recopilarse basura si el cargador de clases ya no es accesible. El inconveniente es que el cargador de clases bootstrap/system nunca se recolecta basura, por lo tanto, los objetos accesibles desde las clases cargadas por el cargador de clases del sistema tampoco se pueden recolectar basura.

El razonamiento para este comportamiento se explica en JLS. Por ejemplo, Third Edition 12.7 http://java.sun.com/docs/books/jls/third_edition/html/execution.html#12.7.

+0

En realidad, pensamos que ya lo habíamos descifrado, pero desafortunadamente ahora parece que no lo tenemos . Pero sí, esta es una posibilidad que debemos explorar. –

2

Una referencia circular ocurre cuando un objeto se refiere a otro, y ese otro se refiere al primer objeto. Por ejemplo:

class A { 
private B b; 

public void setB(B b) { 
    this.b = b; 
} 
} 

class B { 
private A a; 

public void setA(A a) { 
    this.a = a; 
} 
} 

public class Main { 
public static void main(String[] args) { 
    A one = new A(); 
    B two = new B(); 

    // Make the objects refer to each other (creates a circular reference) 
    one.setB(two); 
    two.setA(one); 

    // Throw away the references from the main method; the two objects are 
    // still referring to each other 
    one = null; 
    two = null; 
} 
} 

recolector de basura de Java es lo suficientemente inteligente como para limpiar los objetos si hay referencias circulares, pero no hay discusiones en vivo que tienen todas las referencias a los objetos más. Por lo tanto, tener una referencia circular como esta no crea una pérdida de memoria.

Cuestiones relacionadas