java garbage-collection member gc-roots

java - ¿Por qué un campo no estático no actúa como una raíz de GC?



garbage-collection member (2)

Un campo no estático tiene una referencia mantenida por la instancia que lo contiene, por lo que no puede ser una raíz de GC por derecho propio.

Como sé que los campos estáticos (junto con los hilos, las variables locales y los argumentos del método, las referencias JNI) actúan como raíces de GC.

No puedo proporcionar un enlace que confirme esto, pero he leído muchos artículos sobre él.

¿Por qué no puede un campo no estático actuar como una raíz de GC?


En primer lugar, debemos asegurarnos de estar en sintonía con lo que hace un algoritmo de recolección de basura en su fase de marca.

En cualquier momento dado, un GC de rastreo tiene una serie de objetos que se sabe que están vivos, en el sentido de que son accesibles por el programa en ejecución tal como está ahora. El paso principal de la frase de marca implica seguir los campos no estáticos de esos objetos para encontrar más objetos, y ahora también se sabe que esos objetos nuevos están vivos. Este paso se repite recursivamente hasta que no se encuentran nuevos objetos vivos al atravesar los objetos vivos existentes. Todos los objetos en la memoria no probados en vivo se consideran muertos. (El GC luego pasa a la siguiente fase, que se llama fase de barrido. No nos importa esa fase para esta respuesta).

Ahora esto solo no es suficiente para ejecutar el algoritmo. Al principio, el algoritmo no tiene objetos que sabe que están vivos, por lo que no puede comenzar a seguir los campos no estáticos de nadie. Necesitamos especificar un conjunto de objetos que se sabe están vivos desde el principio. Elegimos esos objetos axiomáticamente, en el sentido de que no provienen de un paso previo del algoritmo: vienen del exterior. Específicamente, provienen de la semántica del lenguaje. Esos objetos se llaman raíces.

En un lenguaje como Java, hay dos conjuntos de objetos que son raíces definidas de GC. Todo lo que es accesible por una variable local que todavía está en el alcance es obviamente alcanzable (dentro de su método, que aún no ha regresado), por lo tanto está vivo, por lo tanto, es una raíz. Todo lo que es accesible a través de un campo estático de una clase también es obviamente accesible (desde cualquier lugar), por lo tanto está vivo, por lo tanto, es una raíz.

Pero si los campos no estáticos también se consideraran raíces, ¿qué pasaría?

Digamos que crea una instancia de ArrayList<E> . En el interior, ese objeto tiene un campo no estático que apunta a un Object[] (la matriz de respaldo que representa el almacenamiento de la lista). En algún momento, comienza un ciclo GC. En la fase de marca, el Object[] se marca como vivo porque apunta al campo privado no estático ArrayList<E> . ArrayList<E> no apunta a nada, por lo que no se considera vivo. Por lo tanto, en este ciclo, ArrayList<E> se destruye mientras el Object[] respaldo Object[] sobrevive. Por supuesto, en el siguiente ciclo, el Object[] también muere, porque no es alcanzable por ninguna raíz. Pero, ¿por qué hacer esto en dos ciclos? Si el ArrayList<E> estaba muerto en el primer ciclo y si el Object[] es usado solo por un objeto muerto, ¿no debería el Object[] también considerarse muerto en el mismo movimiento, para ahorrar tiempo y espacio?

Ese es el punto aquí. Si queremos ser máximamente eficientes (en el contexto de un GC de rastreo), tenemos que deshacernos de tantos objetos muertos como sea posible en un solo GC.

Para hacer eso, un campo no estático debería mantener un objeto vivo solo si se ha demostrado que el objeto que lo rodea (el objeto que contiene el campo) está vivo. Por el contrario, las raíces son objetos que llamamos vivos axiomáticamente (sin pruebas) para poner en marcha la fase de marcado del algoritmo. Nos conviene limitar la última categoría al mínimo indispensable que no interrumpe el programa en ejecución.

Por ejemplo, digamos que tienes este código:

class Foo { Bar bar = new Bar(); public static void main(String[] args) { Foo foo = new Foo(); System.gc(); } public void test() { Integer a = 1; bar.counter++; //access to the non-static field } } class Bar { int counter = 0; }

  • Cuando se inicia la recolección de basura, obtenemos una raíz que es la variable local Foo foo . Eso es, esa es nuestra única raíz.
  • Seguimos la raíz para encontrar la instancia de Foo , que está marcada como viva y luego intentamos encontrar sus campos no estáticos. Encontramos uno de ellos, el campo Bar bar .
  • Seguimos los campos para encontrar la instancia de Bar , que está marcada como viva y luego intentamos encontrar sus campos no estáticos. Encontramos que no contiene más campos que sean tipos de referencia, por lo que el GC ya no necesita preocuparse por ese objeto.
  • Como no podemos encontrar nuevos objetos vivos en esta ronda de recursión, la fase de marca puede finalizar.

Alternativamente:

class Foo { Bar bar = new Bar(); public static void main(String[] args) { Foo foo = new Foo(); foo.test(); } public void test() { Integer a = 1; bar.counter++; //access to the non-static field System.gc(); } } class Bar { int counter = 0; }

  • Cuando se inicia la recolección de basura, la variable local Integer a es una raíz y el Foo this referencia (la referencia implícita que obtienen todos los métodos no estáticos) también es una raíz. La variable local Foo foo de main también es una raíz porque main no ha salido aún del alcance.
  • Seguimos la raíz para encontrar la instancia de Integer e instancia de Foo (encontramos uno de estos objetos dos veces, pero esto no importa para el algoritmo), que están marcados como vivos y luego intentamos seguir sus campos no estáticos . Digamos que la instancia de Integer no tiene más campos para las instancias de la clase. La instancia de Foo nos da un campo de Bar .
  • Seguimos el campo para encontrar la instancia de Bar , que está marcada como viva y luego intentamos encontrar sus campos no estáticos. Encontramos que no contiene más campos que sean tipos de referencia, por lo que el GC ya no necesita preocuparse por ese objeto.
  • Como no podemos encontrar nuevos objetos vivos en esta ronda de recursión, la fase de marca puede finalizar.