¿La asignación de objetos a nulos en Java afecta la recolección de basura?

85

¿La asignación de una referencia de objeto no utilizada nullen Java mejora el proceso de recolección de basura de alguna manera mensurable?

Mi experiencia con Java (y C #) me ha enseñado que a menudo es contrario a la intuición intentar ser más astuto que la máquina virtual o el compilador JIT, pero he visto a compañeros de trabajo usar este método y tengo curiosidad por saber si esta es una buena práctica para elegir. o una de esas supersticiones de programación vudú?

James McMahon
fuente

Respuestas:

66

Normalmente, no.

Pero como todas las cosas: depende. El GC en Java en estos días es MUY bueno y todo debería limpiarse muy poco después de que ya no sea accesible. Esto es justo después de dejar un método para variables locales y cuando ya no se hace referencia a una instancia de clase para campos.

Solo necesita anular explícitamente si sabe que, de lo contrario, permanecería referenciado. Por ejemplo, una matriz que se mantiene alrededor. Es posible que desee anular los elementos individuales de la matriz cuando ya no sean necesarios.

Por ejemplo, este código de ArrayList:

public E remove(int index) {
    RangeCheck(index);

    modCount++;
    E oldValue = (E) elementData[index];

    int numMoved = size - index - 1;
    if (numMoved > 0)
         System.arraycopy(elementData, index+1, elementData, index,
             numMoved);
    elementData[--size] = null; // Let gc do its work

    return oldValue;
}

Además, anular explícitamente un objeto no hará que se recopile un objeto antes que si simplemente saliera del alcance de forma natural siempre que no queden referencias.

Ambos:

void foo() {
   Object o = new Object();
   /// do stuff with o
}

y:

void foo() {
   Object o = new Object();
   /// do stuff with o
   o = null;
}

Son funcionalmente equivalentes.

Mark Renouf
fuente
7
También es posible que desee anular las referencias dentro de métodos que pueden usar una gran cantidad de memoria o tardar mucho en ejecutarse, o en el código de una aplicación Swing que puede ejecutarse durante mucho tiempo. En métodos cortos u objetos de corta duración, no vale la pena el código extra confuso.
John Gardner
1
Entonces, ¿es correcto, cuando anulamos un objeto, el recolector de basura recolecta ese objeto inmediatamente?
Muhammad Babar
1
No. El recolector de basura se ejecuta periódicamente para ver si hay objetos a los que no apuntan variables de referencia. Cuando los configura como nulos y luego los llama System.gc(), se eliminará (siempre que no haya otras referencias que apunten a ese objeto).
JavaTechnical
2
@JavaTechnical Como sé, no se garantiza que al llamar a System.gc () se inicie la recolección de basura.
Jack
12

En mi experiencia, la mayoría de las veces, las personas anulan las referencias por paranoia, no por necesidad. Aquí hay una guía rápida:

  1. Si el objeto A hace referencia al objeto B y ya no necesita esta referencia y el objeto A no es elegible para la recolección de basura, entonces debe anular explícitamente el campo. No es necesario anular un campo si el objeto adjunto está obteniendo la basura recolectada de todos modos. Anular campos en un método dispose () casi siempre es inútil.

  2. No es necesario anular las referencias a objetos creadas en un método. Se borrarán automáticamente una vez que finalice el método. La excepción a esta regla es si está ejecutando un método muy largo o un bucle masivo y necesita asegurarse de que algunas referencias se borren antes del final del método. Nuevamente, estos casos son extremadamente raros.

Yo diría que la gran mayoría de las veces no necesitará anular referencias. Tratar de burlar al recolector de basura es inútil. Terminará con un código ineficiente e ilegible.

Gili
fuente
11

El buen artículo es el horror de la codificación de hoy .

La forma en que GC trabaja es buscando objetos que no tengan punteros, el área de su búsqueda es montón / pila y cualquier otro espacio que tengan. Entonces, si establece una variable en nula, el objeto real ahora no es señalado por nadie y, por lo tanto, podría ser GC.

Pero dado que es posible que el GC no se ejecute en ese instante exacto, es posible que no se esté comprando nada. Pero si su método es bastante largo (en términos de tiempo de ejecución), podría valer la pena, ya que aumentará sus posibilidades de que GC recopile ese objeto.

El problema también puede complicarse con las optimizaciones de código, si nunca usa la variable después de establecerla en nulo, sería una optimización segura eliminar la línea que establece el valor en nulo (una instrucción menos para ejecutar). Por lo tanto, es posible que no obtenga ninguna mejora.

Entonces, en resumen, sí puede ayudar, pero no será determinista .

EarnNameless
fuente
Buen punto sobre la optimización segura que elimina la línea que establece la referencia en nulo.
pregunta
8

Al menos en Java, no es programación vudú en absoluto. Cuando creas un objeto en java usando algo como

Foo bar = new Foo();

haces dos cosas: primero, creas una referencia a un objeto, y segundo, creas el objeto Foo en sí. Siempre que exista esa referencia u otra, el objeto específico no se puede controlar. sin embargo, cuando asigna nulo a esa referencia ...

bar = null ;

y asumiendo que nada más tiene una referencia al objeto, se libera y está disponible para gc la próxima vez que pase el recolector de basura.

Charlie martin
fuente
12
Pero salir del alcance hará lo mismo sin la línea adicional de código. Si es local para un método, no es necesario. Una vez que abandone el método, el objeto será elegible para GC.
duffymo
2
Bueno. ahora, para un examen sorpresa, construya un ejemplo de código para el que NO sea suficiente. Pista: existen.
Charlie Martin
7

Depende.

En términos generales, si mantiene las referencias a sus objetos, más rápido se recopilarán.

Si su método tarda, digamos, 2 segundos en ejecutarse y ya no necesita un objeto después de un segundo de ejecución del método, tiene sentido borrar cualquier referencia a él. Si GC ve que después de un segundo, su objeto todavía está referenciado, la próxima vez podría verificarlo en un minuto más o menos.

De todos modos, establecer todas las referencias en nulo de forma predeterminada es para mí una optimización prematura y nadie debería hacerlo a menos que en casos raros específicos en los que disminuya considerablemente el consumo de memoria.

lubos hasko
fuente
6

Establecer explícitamente una referencia a nulo en lugar de simplemente dejar que la variable salga del alcance, no ayuda al recolector de basura, a menos que el objeto retenido sea muy grande, donde establecerlo en nulo tan pronto como haya terminado es una buena idea.

Generalmente, establecer referencias a nulo, significa para el LECTOR del código que este objeto está completamente terminado y no debería preocuparse por nada más.

Se puede lograr un efecto similar introduciendo un alcance más estrecho colocando un juego adicional de tirantes

{
  int l;
  {  // <- here
    String bigThing = ....;
    l = bigThing.length();
  }  // <- and here
}

esto permite que bigThing se recolecte como basura justo después de dejar las llaves anidadas.

Thorbjørn Ravn Andersen
fuente
Buena alternativa. Esto evita establecer explícitamente la variable en nulo y la pelusa que advierte que algunos IDE muestran que la asignación nula no se está utilizando.
jk7
5
public class JavaMemory {
    private final int dataSize = (int) (Runtime.getRuntime().maxMemory() * 0.6);

    public void f() {
        {
            byte[] data = new byte[dataSize];
            //data = null;
        }

        byte[] data2 = new byte[dataSize];
    }

    public static void main(String[] args) {

        JavaMemory jmp = new JavaMemory();
        jmp.f();

    }

}

Lanzamientos del programa anterior OutOfMemoryError. Si descomenta data = null;, el OutOfMemoryErrorestá resuelto. Siempre es una buena práctica establecer la variable no utilizada en nula

omiel
fuente
Eso es, en mi opinión, una sólida incursión en el mundo de los argumentos del hombre de paja. El hecho de que PUEDA crear una situación en la que establecer la variable en nulo resolverá el problema en ese caso en particular (y no estoy convencido de que lo haya hecho) no implica que establecerlo en nulo sea una buena idea en todos los casos. Agregar el código para establecer todas las variables que ya no está usando en nulos, incluso cuando saldrían del alcance poco después, solo aumenta la hinchazón del código y hace que el código sea menos mantenible.
RHSeeger
¿Por qué no se recoge la basura de la matriz de bytes [] en el momento en que los datos variables están fuera de alcance?
Grav
2
@Grav: salir del alcance no garantiza que el recolector de basura recolecte los objetos locales. Sin embargo, establecerlo en nulo evita la excepción OutOfMemory, porque tiene la garantía de que el GC se ejecutará antes de recurrir a lanzar una excepción OutOfMemory, y si el objeto se estableció en nulo, será coleccionable.
Stefanos T.
4

Una vez estaba trabajando en una aplicación de videoconferencia y noté una enorme diferencia en el rendimiento cuando me tomé el tiempo para anular referencias tan pronto como ya no necesitaba el objeto. Esto fue en 2003-2004 y solo puedo imaginar que el GC se ha vuelto aún más inteligente desde entonces. En mi caso, tenía cientos de objetos yendo y viniendo fuera de alcance cada segundo, así que noté el GC cuando se activaba periódicamente. Sin embargo, después de que hice un punto a los objetos nulos, el GC dejó de pausar mi aplicación.

Entonces depende de lo que estés haciendo ...

Sidra de pera
fuente
2

Si.

De "El programador pragmático" p.292:

Al establecer una referencia a NULL, reduce el número de punteros al objeto en uno ... (lo que permitirá que el recolector de basura lo elimine)


fuente
Supongo que esto es aplicable solo cuando el algoritmo de recuento de referencias. está en uso o también de otra manera?
Nrj
3
Este consejo es obsoleto para cualquier recolector de basura moderno; y el recuento de referencias GC tiene problemas importantes, como las referencias circulares.
Lawrence Dol
También sería cierto en una GC de marca y barrido; probablemente en todos los demás. El hecho de que tenga una referencia menos no significa que se cuente. La respuesta de tweakt explica esto. Es mejor reducir el alcance que establecerlo en NULL.
1

Supongo que el OP se refiere a cosas como esta:

private void Blah()
{
    MyObj a;
    MyObj b;

    try {
        a = new MyObj();
        b = new MyObj;

        // do real work
    } finally {
        a = null;
        b = null;
    }
}

En este caso, ¿no los marcaría la VM para GC tan pronto como dejen el alcance de todos modos?

¿O, desde otra perspectiva, establecer explícitamente los elementos en nulos provocaría que obtengan GC antes que lo harían si simplemente salieran del alcance? Si es así, la máquina virtual puede pasar tiempo haciendo GC en el objeto cuando la memoria no es necesaria de todos modos, lo que en realidad causaría un peor rendimiento en el uso de la CPU porque estaría haciendo GC más antes.

Codificación con espiga
fuente
El momento en el que se ejecuta el GC no es determinista. No creo que establecer un objeto nullinfluya en absoluto en el comportamiento de GC.
harto
0

" Depende "

No sé sobre Java, pero en .net (C #, VB.net ...) generalmente no es necesario asignar un valor nulo cuando ya no necesita un objeto.

Sin embargo, tenga en cuenta que "normalmente no es obligatorio".

Al analizar su código, el compilador .net hace una buena valoración del tiempo de vida de la variable ... para saber con precisión cuándo el objeto ya no se usa. Entonces, si escribe obj = null, en realidad podría parecer que el obj todavía se está usando ... en este caso, es contraproducente asignar un valor nulo.

Hay algunos casos en los que podría resultar útil asignar un valor nulo. Un ejemplo es que tiene un código enorme que se ejecuta durante mucho tiempo o un método que se ejecuta en un hilo diferente, o algún bucle. En tales casos, podría ser útil asignar un valor nulo para que sea fácil para el GC saber que ya no se usa.

No existe una regla estricta para esto. Pasando por el lugar anterior asigna nulos en su código y ejecute un generador de perfiles para ver si ayuda de alguna manera. Lo más probable es que no vea ningún beneficio.

Si está tratando de optimizar el código .net, entonces mi experiencia ha sido que tener mucho cuidado con los métodos Dispose y Finalize es en realidad más beneficioso que preocuparse por los nulos.

Algunas referencias sobre el tema:

http://blogs.msdn.com/csharpfaq/archive/2004/03/26/97229.aspx

http://weblogs.asp.net/pwilson/archive/2004/02/20/77422.aspx

Sesh
fuente
0

Incluso si anular la referencia fuera marginalmente más eficiente, ¿valdría la pena la fealdad de tener que salpicar su código con estas horribles anulaciones? Solo serían desorden y oscurecerían el código de intención que los contiene.

Es una base de código poco común que no tiene mejor candidato para la optimización que intentar burlar al recolector de basura (más raros aún son los desarrolladores que logran burlarlo). Lo más probable es que sus esfuerzos se gasten mejor en otro lugar, abandonando ese crujiente analizador Xml o encontrando alguna oportunidad para almacenar en caché el cálculo. Estas optimizaciones serán más fáciles de cuantificar y no requieren que ensucie su código base con ruido.

sgargan
fuente
0

En la ejecución futura de su programa, los valores de algunos miembros de datos se usarán para computar una salida visible externa al programa. Otros pueden usarse o no, dependiendo de las entradas futuras (e imposibles de predecir) al programa. Se puede garantizar que no se utilizarán otros miembros de datos. Todos los recursos, incluida la memoria, asignados a los datos no utilizados se desperdician. El trabajo del recolector de basura (GC) es eliminar esa memoria desperdiciada. Sería desastroso para el GC eliminar algo que se necesitaba, por lo que el algoritmo utilizado podría ser conservador, reteniendo más que el mínimo estricto. Podría usar optimizaciones heurísticas para mejorar su velocidad, a costa de retener algunos elementos que en realidad no son necesarios. Hay muchos algoritmos potenciales que podría utilizar el GC. Por lo tanto, es posible que los cambios que realice en su programa, y que no afectan la corrección de su programa, podrían afectar el funcionamiento del GC, ya sea para que se ejecute más rápido para hacer el mismo trabajo o para identificar antes los elementos no utilizados. Entonces, este tipo de cambio, estableciendo una referencia de objeto unusdd anull, en teoría no siempre es vudú.

¿Es vudú? Según se informa, hay partes del código de la biblioteca de Java que hacen esto. Los escritores de ese código son mucho mejores que los programadores promedio y conocen o cooperan con los programadores que conocen los detalles de las implementaciones del recolector de basura. Por lo que sugiere que es a veces un beneficio.

Raedwald
fuente
0

Como dijiste, hay optimizaciones, es decir, JVM conoce el lugar cuando la variable se usó por última vez y el objeto al que hace referencia puede ser GCed justo después de este último punto (aún ejecutándose en el alcance actual). Así que anular las referencias en la mayoría de los casos no ayuda a GC.

Pero puede ser útil para evitar el problema del "nepotismo" (o "basura flotante") ( lea más aquí o vea el video ). El problema existe porque el montón se divide en generaciones viejas y jóvenes y se aplican diferentes mecanismos de GC: Minor GC (que es rápido y suele limpiar la generación joven) y Major Gc (que provoca una pausa más prolongada para limpiar Old gen). El "nepotismo" no permite que la basura en la generación joven sea recolectada si se hace referencia a la basura que ya estaba en posesión de una generación anterior.

Esto es "patológico" porque CUALQUIER nodo promocionado dará como resultado la promoción de TODOS los siguientes nodos hasta que un GC resuelva el problema.

Para evitar el nepotismo, es una buena idea anular las referencias de un objeto que se supone que debe eliminarse. Puede ver esta técnica aplicada en las clases de JDK: LinkedList y LinkedHashMap

private E unlinkFirst(Node<E> f) {
    final E element = f.item;
    final Node<E> next = f.next;
    f.item = null;
    f.next = null; // help GC
    // ...
}
Kirill
fuente