Hoy mis colegas y yo tenemos una discusión sobre el uso de la final
palabra clave en Java para mejorar la recolección de basura.
Por ejemplo, si escribe un método como:
public Double doCalc(final Double value)
{
final Double maxWeight = 1000.0;
final Double totalWeight = maxWeight * value;
return totalWeight;
}
Declarar las variables en el método final
ayudaría a la recolección de basura a limpiar la memoria de las variables no utilizadas en el método después de que el método salga.
¿Es esto cierto?
java
garbage-collection
final
Goran Martinic
fuente
fuente
Respuestas:
Aquí hay un ejemplo ligeramente diferente, uno con campos de tipo de referencia final en lugar de variables locales de tipo de valor final:
public class MyClass { public final MyOtherObject obj; }
Cada vez que crea una instancia de MyClass, creará una referencia saliente a una instancia de MyOtherObject, y el GC tendrá que seguir ese enlace para buscar objetos activos.
La JVM utiliza un algoritmo GC de barrido de marca, que tiene que examinar todas las referencias en vivo en las ubicaciones "raíz" de GC (como todos los objetos en la pila de llamadas actual). Cada objeto vivo se "marca" como vivo, y cualquier objeto al que se refiere un objeto vivo también se marca como vivo.
Después de la finalización de la fase de marca, el GC barre el montón, liberando memoria para todos los objetos sin marcar (y compactando la memoria para los objetos vivos restantes).
Además, es importante reconocer que la memoria del montón de Java está dividida en una "generación joven" y una "generación anterior". Todos los objetos se asignan inicialmente a la generación joven (a veces denominada "la guardería"). Dado que la mayoría de los objetos son de corta duración, el GC es más agresivo en cuanto a liberar la basura reciente de la generación joven. Si un objeto sobrevive a un ciclo de recolección de la generación joven, se traslada a la generación anterior (a veces denominada "generación titular"), que se procesa con menos frecuencia.
Entonces, en lo alto de mi cabeza, voy a decir "no, el modificador 'final' no ayuda al GC a reducir su carga de trabajo".
En mi opinión, la mejor estrategia para optimizar la gestión de la memoria en Java es eliminar las referencias falsas lo más rápido posible. Puede hacerlo asignando "nulo" a una referencia de objeto tan pronto como haya terminado de usarlo.
O, mejor aún, minimice el tamaño del alcance de cada declaración. Por ejemplo, si declara un objeto al comienzo de un método de 1000 líneas, y si el objeto permanece vivo hasta el cierre del alcance de ese método (la última llave de cierre), entonces el objeto podría permanecer vivo por mucho más tiempo que en realidad necesario.
Si usa métodos pequeños, con solo una docena de líneas de código, entonces los objetos declarados dentro de ese método caerán fuera del alcance más rápidamente, y el GC podrá hacer la mayor parte de su trabajo dentro de la mucho más eficiente generación joven. No desea que los objetos pasen a la generación anterior a menos que sea absolutamente necesario.
fuente
Declarar una variable local
final
no afectará la recolección de basura, solo significa que no puede modificar la variable. Su ejemplo anterior no debería compilarse ya que está modificando la variabletotalWeight
que se ha marcadofinal
. Por otro lado, declarar una primitiva (endouble
lugar deDouble
)final
permitirá que esa variable se inserte en el código de llamada, por lo que podría causar alguna mejora de memoria y rendimiento. Esto se usa cuando tienes variospublic static final Strings
en una clase.En general, el compilador y el tiempo de ejecución optimizarán donde sea posible. Es mejor escribir el código apropiadamente y no intentar ser demasiado complicado. Úselo
final
cuando no desee que se modifique la variable. Suponga que el compilador realizará las optimizaciones fáciles y, si le preocupa el rendimiento o el uso de la memoria, utilice un generador de perfiles para determinar el problema real.fuente
No, enfáticamente no es cierto.
Recuerde que eso
final
no significa constante, solo significa que no puede cambiar la referencia.final MyObject o = new MyObject(); o.setValue("foo"); // Works just fine o = new MyObject(); // Doesn't work.
Puede haber una pequeña optimización basada en el conocimiento de que la JVM nunca tendrá que modificar la referencia (como no tener que verificar si ha cambiado) pero sería tan pequeña como para no preocuparse.
Final
debe considerarse como metadatos útiles para el desarrollador y no como una optimización del compilador.fuente
Algunos puntos a aclarar:
Anular la referencia no debería ayudar a GC. Si lo hiciera, indicaría que sus variables están fuera de alcance. Una excepción es el caso del nepotismo de objetos.
Todavía no hay una asignación en la pila en Java.
Declarar una variable final significa que no puede (en condiciones normales) asignar un nuevo valor a esa variable. Dado que final no dice nada sobre el alcance, no dice nada sobre su efecto en GC.
fuente
Bueno, no sé sobre el uso del modificador "final" en este caso, o su efecto en el GC.
Pero puedo decirle esto: su uso de valores en caja en lugar de primitivas (por ejemplo, Double en lugar de double) asignará esos objetos al montón en lugar de a la pila, y producirá basura innecesaria que el GC tendrá que limpiar.
Solo uso primitivas en caja cuando lo requiere una API existente o cuando necesito primitivas que aceptan valores NULL.
fuente
Las variables finales no se pueden cambiar después de la asignación inicial (impuesta por el compilador).
Esto no cambia el comportamiento de la recolección de basura como tal. Lo único es que estas variables no se pueden anular cuando no se usan más (lo que puede ayudar a la recolección de basura en situaciones de memoria limitada).
Debe saber que final permite al compilador hacer suposiciones sobre qué optimizar. Insertar código y no incluir el código que se sabe que no es accesible.
final boolean debug = false; ...... if (debug) { System.out.println("DEBUG INFO!"); }
El println no se incluirá en el código de bytes.
fuente
Hay un caso de esquina no tan conocido con los recolectores de basura generacionales. (Para una breve descripción lea la respuesta de benjismith para una visión más profunda lea los artículos al final).
La idea en los CG generacionales es que la mayoría de las veces solo deben considerarse las generaciones jóvenes. La ubicación de la raíz se escanea en busca de referencias y luego se escanean los objetos de la generación joven. Durante estos barridos más frecuentes, no se comprueba ningún objeto de la generación anterior.
Ahora, el problema proviene del hecho de que no se permite que un objeto tenga referencias a objetos más jóvenes. Cuando un objeto de larga duración (generación anterior) obtiene una referencia a un nuevo objeto, el recolector de basura debe rastrear explícitamente esa referencia (consulte el artículo de IBM sobre el recolector de JVM de hotspot ), lo que en realidad afecta el rendimiento de GC.
La razón por la que un objeto antiguo no puede referirse a uno más joven es que, dado que el objeto antiguo no se comprueba en las colecciones menores, si la única referencia al objeto se mantiene en el objeto antiguo, no se marcará y estaría mal desasignado durante la etapa de barrido.
Por supuesto, como han señalado muchos, la palabra clave final no afecta realmente al recolector de basura, pero garantiza que la referencia nunca se cambiará a un objeto más joven si este objeto sobrevive a las colecciones menores y llega al montón más antiguo.
Artículos:
IBM sobre recolección de basura: historia , en el hotspot JVM y rendimiento . Es posible que estos ya no sean completamente válidos, ya que datan de 2003/04, pero brindan información fácil de leer sobre los CG.
Recolección de basura Sun on Tuning
fuente
GC actúa sobre referencias inalcanzables. Esto no tiene nada que ver con "final", que es simplemente una afirmación de asignación única. ¿Es posible que algún GC de VM pueda hacer uso de "final"? No veo cómo ni por qué.
fuente
final
en variables y parámetros locales no hace ninguna diferencia en los archivos de clase producidos, por lo que no puede afectar el rendimiento en tiempo de ejecución. Si una clase no tiene subclases, HotSpot trata esa clase como si fuera definitiva de todos modos (se puede deshacer más tarde si se carga una clase que rompe esa suposición). Creo que losfinal
métodos son muy similares a las clases.final
on static field puede permitir que la variable se interprete como una "constante en tiempo de compilación" y que javac realice la optimización sobre esa base.final
on fields permite a la JVM cierta libertad para ignorar las relaciones de suceso antes de .fuente
Parece haber muchas respuestas que son conjeturas errantes. La verdad es que no existe un modificador final para las variables locales a nivel de código de bytes. La máquina virtual nunca sabrá que sus variables locales se definieron como finales o no.
La respuesta a su pregunta es un rotundo no.
fuente
final int x; if (cond) x=1; else x=2;
). El compilador, sin la palabra clave final, puede garantizar que una variable sea final o no.Todos los métodos y variables se pueden anular por defecto en las subclases. Si queremos evitar que las subclases anulen a los miembros de la superclase, podemos declararlos como finales usando la palabra clave final. Por ejemplo,
final int a=10;
final void display(){......}
hacer un método final asegura que la funcionalidad definida en la superclase nunca será cambiada de todos modos. De manera similar, el valor de una variable final nunca se puede cambiar. Las variables finales se comportan como variables de clase.fuente
Estrictamente hablando de los campos de instancia ,
final
podría mejorar ligeramente el rendimiento si un GC en particular quiere explotarlo. CuandoGC
ocurre un concurrente (eso significa que su aplicación aún se está ejecutando, mientras que GC está en progreso ), vea esto para una explicación más amplia , los GC deben emplear ciertas barreras cuando se realizan las escrituras y / o lecturas. El enlace que les di explica bastante eso, pero para hacerlo realmente corto: cuando unGC
hace un trabajo simultáneo, todos leen y escriben en el montón (mientras ese GC está en progreso), son "interceptados" y aplicados más tarde; para que la fase de GC concurrente pueda terminar su trabajo.Por
final
ejemplo, los campos, dado que no se pueden modificar (salvo reflexión), estas barreras pueden omitirse. Y esto no es solo teoría pura.Shenandoah GC
los tiene en la práctica (aunque no por mucho tiempo ), y puede hacer, por ejemplo:Y habrá optimizaciones en el algoritmo de GC que lo harán un poco más rápido. Esto se debe a que no habrá barreras que intercepten
final
, ya que nadie debería modificarlas, nunca. Ni siquiera por reflexión o JNI.fuente
Lo único en lo que puedo pensar es que el compilador podría optimizar las variables finales e incorporarlas como constantes en el código, por lo que terminará sin memoria asignada.
fuente
Absolutamente, siempre que acorte la vida del objeto, lo que produce un gran beneficio de la gestión de la memoria, recientemente examinamos la funcionalidad de exportación que tiene variables de instancia en una prueba y otra prueba que tiene una variable local de nivel de método. durante la prueba de carga, JVM arroja un error de memoria en la primera prueba y JVM se detuvo. pero en la segunda prueba, se pudo obtener el informe con éxito debido a una mejor gestión de la memoria.
fuente
La única vez que prefiero declarar las variables locales como finales es cuando:
Yo tengo para hacerlos tan definitivo que puedan ser compartidos con alguna clase anónima (por ejemplo: la creación de hilo de utilidad y dejar que se accede a un cierto valor de encerrar método)
Yo quiero hacerlos final (por ejemplo: un valor que no debe / no quede anulado por error)
fuente