¿Ayuda a GC a anular las variables locales en Java?
garbage-collection null (15)
Me ''obligaron'' a agregar myLocalVar = null;
declaración en la cláusula finally justo antes de abandonar el método. La razón es ayudar a GC. Me dijeron que recibiré mensajes de texto durante la noche cuando el servidor se bloquee la próxima vez, así que es mejor que lo haga :-).
Creo que esto no tiene sentido, ya que myLocalVar tiene el alcance del método, y se ''perderá'' tan pronto como el método salga. La nulidad adicional solo contamina el código, pero de lo contrario es inofensivo.
Mi pregunta es, ¿de dónde viene este mito acerca de ayudar a GC? (Me recomendaron "libros de memoria de Java") ¿Conoces algún artículo de "autoridades" que lo explique con más profundidad? ¿Hay alguna posibilidad de que esto no sea un mito, pero realmente ayude de alguna manera? ¿Si es así, cómo? ¿Puede anular las variables locales causar algún daño?
Para aclarar, el método se ve así:
void method() {
MyClass myLocalVar = null;
try {
myLocalVar = get reference to object;
... do more here ...
} finally {
if (myLocalVar != null) {
myLocalVar.close(); // it is resource which we should close
}
myLocalVar = null; // THIS IS THE LINE I AM TALKING ABOUT
}
}
Anular variables locales puede ayudar en algunos casos extremos. Esto no se aplica a la situación en la pregunta original, pero es educativo de todos modos ... Consideremos este programa:
public class Main {
public static void main(String[] args) {
{
Main local = new Main();
// inner = null;
}
while (true) {
// long running method
}
}
}
Si inner = null;
está comentado, el objeto en local
variable local
no se puede recoger como basura durante el ciclo while. La razón es que Java Virtual Machine no sabe acerca de ámbitos como este. Todo lo que tiene es:
D:/workspaces/workspace-3.4/test/src>javap -verbose -c Main
public class Main extends java.lang.Object
minor version: 0
major version: 50
Constant pool:
const #1 = Method #4.#11; // java/lang/Object."<init>":()V
const #2 = class #12; // Main
const #3 = Method #2.#11; // Main."<init>":()V
const #4 = class #13; // java/lang/Object
const #5 = Asciz <init>;
const #6 = Asciz ()V;
const #7 = Asciz Code;
const #8 = Asciz main;
const #9 = Asciz ([Ljava/lang/String;)V;
const #10 = Asciz StackMapTable;
const #11 = NameAndType #5:#6;// "<init>":()V
const #12 = Asciz Main;
const #13 = Asciz java/lang/Object;
{
public Main();
Code:
Stack=1, Locals=1, Args_size=1
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
Stack=2, Locals=2, Args_size=1
0: new #2; //class Main
3: dup
4: invokespecial #3; //Method "<init>":()V
7: astore_1
8: goto 8
StackMapTable: number_of_entries = 1
frame_type = 8 /* same */
}
No hay información sobre el alcance de la variable local. Entonces, desde el punto de vista de JVM, el programa anterior es equivalente a:
public class Main
{
public Main() { }
public static void main(String args[])
{
Main main1 = new Main();
do
;
while(true);
}
}
(Generado por el descompilador de JAD)
Conclusión: HAY alguna justificación para anular variables locales en casos muy especiales como este. Pero si el método va a terminar pronto (como en mi pregunta original), no ayuda.
Esto fue inspirado por el comentario de Zdenek Tronicek en la lista de correo java-cz (en checo, lo siento)
Como señala correctamente, anular en este caso es totalmente inútil.
De vuelta en JDK1.3 Realmente tenía un caso con un gráfico de objetos extremadamente grande que también contenía muchas referencias circulares dentro del gráfico. Limpiar algunas de las referencias realmente mejoró los tiempos de GC bastante notablemente.
No estoy seguro de si esto se aplicaría a una máquina virtual moderna. Los recolectores de basura se han vuelto cada vez más inteligentes.
En algunas circunstancias, puede ser útil anular variables (generalmente variables de instancia o de clase). Pero anular una variable local inmediatamente antes del final del método no hace absolutamente nada.
Cuando establece una variable en null
, simplemente está eliminando esa referencia al objeto real. Pero cuando una variable local sale del alcance, la referencia se elimina de todos modos; por lo tanto, establecerlo en nulo como la última línea del método es simplemente redundante.
Ese es un mito que data de cuando apareció Java y los chicos de C ++ no confiaban en el gc.
El gc sabe lo que está haciendo. anular var no dañará nada, pero tampoco ayudará en nada. Jeff tuvo una publicación bastante divertida sobre esto el otro día.
Estás en lo correcto. Anular una variable que inmediatamente se saldrá de alcance de todos modos es innecesaria y no hace diferencia alguna a GC. Todo lo que hace es desordenar el código. En Effective Java 2nd Edition , el autor recomienda evitar la anulación innecesaria de variables locales. Ver Effective Java, 2nd Edition, Item 6: Eliminar las referencias obsoletas a los objetos, para una descripción completa.
También puede ver esto en el artículo Creación y destrucción de objetos Java , en InformIT. Lea el artículo completo para encontrar el lugar donde Joshua Bloch está de acuerdo con usted.
Cuando una variable local cae fuera del alcance, es exactamente lo mismo que si anulas la referencia a ella.
EDITAR: Agregar enlace a Effective Java 2nd Edition en el sitio web de Sun
Había una vieja pieza de documentación de Sun, Java Platform Performance (lamentablemente, el enlace está roto y no he podido encontrar uno nuevo), que describía una situación en la que anular una variable local que se salía del alcance realmente tenía un efecto en el GC.
Sin embargo, el documento se refirió a una versión muy antigua de Java. Como se menciona en esta pregunta (que también contiene un resumen del problema descrito en el documento), esto ya no afecta a las implementaciones de JVM actuales.
No conozco los detalles técnicos, pero, por lo que puedo recordar, la variable no es más que una referencia del marco de pila actual, y hasta que se elimine esta referencia, el objeto no puede ser recolectado como basura. Ahora, pero estableciéndolo explícitamente en nulo, se ha asegurado de que la referencia haya desaparecido. Si no lo haces, básicamente estás dejando que la máquina virtual decida cuándo se borra esta referencia, lo que podría o no ser al salir del alcance (a diferencia de C ++, si el objeto está ubicado en la pila y DEBE destruirse). Puede ser cuando el marco de pila se sobrescribe con el siguiente. No estoy seguro de si hay una máquina virtual que hace esto.
Respuesta corta, sin embargo, es innecesario, la marca y el barrido lo obtendrán con el tiempo. Es como mucho una cuestión de tiempo.
No en este caso. myLocalVar se sale del alcance tan pronto como la función retorna, por lo que establecer la referencia a null no hace absolutamente nada.
No solo anular una variable local así sin sentido en términos de GC, puede provocar la carga innecesaria de la variable en un registro para anularla, lo que empeora la situación. Imagine si hay 1000 líneas de código entre la última lectura o escritura en myLocalVar, y luego la referencia solo para anular la referencia. El valor ha desaparecido del registro, pero tiene que volver a cargarlo en la memoria para trabajar con él.
Otro posible factor en este mito es que puede marcar la diferencia anular una variable local si ya terminaste con ella antes de que finalice el método. Esto permitiría al GC recoger ese objeto antes de que el método se complete, lo que podría ser útil.
Alguien podría haber recibido ese consejo en algún momento y lo malinterpretó como una necesidad de anular siempre las variables locales.
Se supone que Java GC es "sólido" pero no necesariamente "completo" inmediatamente. En otras palabras, está diseñado para que nunca elimine objetos a los que todavía se puede acceder mediante al menos una ruta (y, por lo tanto, causa una referencia que cuelga). No necesariamente se completa inmediatamente, ya que puede tomar tiempo hasta que elimine todo lo que se puede eliminar.
Creo que la mayoría de los mitos de GC provienen de un malentendido de ese concepto. Muchas personas mantienen demasiadas variables de instancia con vida, y eso causa problemas, pero ese no es el problema aquí.
Otras personas ponen las variables locales en una variable de instancia (por ejemplo, pasándola a la función), y luego piensan que anulando la variable local de alguna manera elimina la variable, lo que por supuesto es falso.
Finalmente, hay personas que están excesivamente en el GC y piensan que les haría un apagado funcional (por ejemplo, conexiones cercanas cuando se elimina la variable), lo que por supuesto no es el caso. Creo que la fuente de esta línea es "Realmente he terminado con esto, pero no estoy seguro de cómo asegurarlo".
Así que sí, tienes razón en que es innecesario.
Según mi leal saber y entender, null
una variable inmediatamente antes de que salga del alcance no hace ninguna diferencia para el recolector de basura.
Por supuesto, hay casos en los que sí ayuda. Por ejemplo, cuando var
no es una variable local sino un miembro o miembro estático. Entonces, destruir la referencia puede hacer que el objeto sea inalcanzable y, por lo tanto, elegible para la recopilación.
Otro caso donde podría ayudar incluso con variables locales si una función asigna mucha memoria temporal para inicializar algunos datos para un procesamiento posterior y puede descartar todas las referencias a la memoria temporal antes de comenzar el procesamiento:
SomeResult SomeFunction(SomeClass param) {
TempData big = new TempData(param);
IntermediateResult intermediate = big.GetIntermediateResult();
big = null; // allow GC to reclaim the memory before returning from the function
intermediate.FurtherProcessing();
return intermediate.EvenMoreProcessing();
}
Si su clase se queda por mucho tiempo, anular los objetos a los que hace referencia les permitirá recopilarlos.
Esto casi nunca es un problema, la mayoría de las veces anular objetos no es útil.
Cuando piense en la asignación y liberación de objetos, preste atención a las cosas que maneja el "Sistema": hilos activos, ventanas que no han sido eliminadas () d, y una o dos cosas más, pero no recuerdo bien ahora.
Cada objeto en su sistema "se cuelga" de estos puntos de montaje en un gigantesco árbol invertido. Si cortas cualquier rama libre de estas "Raíces", toda la rama cae al suelo y es recogida por el Cortacésped de Recolección de Basura.
La mayoría de las clases necesitan todas las variables de sus miembros para todo su ciclo de vida, y cuando su vida finaliza, se recorta toda su sucursal, incluidos todos sus miembros; por lo tanto, no es necesario anular.
(estos adornos, por cierto, son bastante eficientes, incluso más que los de C ++, ya que no requieren tocar cada objeto a medida que se libera)
Si ya no necesita objetos grandes en su ámbito local, puede darle una pista a la JVM y establecer la referencia NULL.
public void foobar()
{
List<SomeObject> dataList = new ArrayList<SomeObject>();
// heavy computation here where objects are added to dataList
// and the list grows, maybe you will sort the list and
// you just need the first element...
SomeObject smallest = dataList.get(0);
// more heavy computation will follow, where dataList is never used again
// so give the JVM a hint to drop it on its on discretion
dataList = null;
// ok, do your stuff other heavy stuff here... maybe you even need the
// memory that was occupied by dataList before...
// end of game and the JVM will take care of everything, no hints needed
}
Pero no tiene sentido antes de la devolución, porque esto es hecho por la JVM automáticamente. Así que estoy de acuerdo con todas las publicaciones anteriores.
Solo he encontrado dos casos en los que he encontrado que establecer una variable como nulo ha sido útil:
- En pruebas unitarias que crean un objeto grande en un campo. El probador de la unidad puede retener el objeto de prueba y los objetos que usted creó durante la vida de todas las pruebas. Esto puede causar que el probador se quede sin memoria. En este caso, a menudo es mejor usar variables locales en lugar de campos, pero si un campo lo requiere, se puede borrar en el tearDown.
- El GC puede limpiar las referencias circulares, pero no con una simple colección incremental. Esto puede significar que los objetos con referencias circulares requieren más trabajo para despejarse y pueden vivir mucho más de lo que lo harían de otra manera. En general, esto no importa, pero si está tratando de reducir su tiempo completo de GC, puede ayudar a romper las referencias circulares.