¿Por qué no llamas explícitamente a finalize() o inicias el recolector de basura?

StackOverflow https://stackoverflow.com/questions/28949

  •  09-06-2019
  •  | 
  •  

Pregunta

Despues de leer esta pregunta, Me acordé de cuando me enseñaron Java y me dijeron que nunca llamara a finalize() ni ejecutara el recolector de basura porque "es una gran caja negra de la que nunca debes preocuparte".¿Alguien puede resumir el razonamiento de esto en unas pocas frases?Estoy seguro de que podría leer un informe técnico de Sun sobre este asunto, pero creo que una respuesta agradable, breve y sencilla satisfaría mi curiosidad.

¿Fue útil?

Solución

La respuesta corta:La recolección de basura de Java es una herramienta muy optimizada.System.gc() es un mazo.

El montón de Java se divide en diferentes generaciones, cada una de las cuales se recopila mediante una estrategia diferente.Si adjunta un generador de perfiles a una aplicación saludable, verá que muy rara vez tiene que ejecutar los tipos de colecciones más costosos porque la mayoría de los objetos son capturados por el recopilador de copias más rápido de la generación más joven.

Llamar a System.gc() directamente, aunque técnicamente no se garantiza que haga nada, en la práctica desencadenará una colección de montón completa y costosa que detendrá el mundo.Esto es casi siempre es lo incorrecto.Cree que está ahorrando recursos, pero en realidad los está desperdiciando sin una buena razón, lo que obliga a Java a volver a verificar todos sus objetos activos "por si acaso".

Si tiene problemas con las pausas del GC durante momentos críticos, es mejor que configure la JVM para usar el recolector de marcas/barrido simultáneo, que fue diseñado específicamente para minimizar el tiempo de pausa, que tratar de solucionar el problema con un mazo y simplemente rompiéndolo aún más.

El documento de Sun en el que estaba pensando está aquí: Ajuste de recolección de basura de la máquina virtual Java SE 6 HotSpot™

(Otra cosa que quizás no sepas:implementar un método finalize() en su objeto hace que la recolección de basura sea más lenta.En primer lugar, será necesario dos GC se ejecuta para recolectar el objeto:uno para ejecutar finalize() y el siguiente para garantizar que el objeto no resucitó durante la finalización.En segundo lugar, los objetos con métodos finalize() deben ser tratados como casos especiales por el GC porque deben recopilarse individualmente, no pueden desecharse en masa).

Otros consejos

No te molestes con los finalizadores.

Cambie a la recolección de basura incremental.

Si desea ayudar al recolector de basura, anule las referencias a objetos que ya no necesita.Menos camino a seguir = más explícitamente basura.

No olvide que las instancias de clase interna (no estáticas) mantienen referencias a su instancia de clase principal.Por lo tanto, un hilo de clase interno guarda mucho más equipaje del que cabría esperar.

En un sentido muy relacionado, si está utilizando la serialización y ha serializado objetos temporales, necesitará borrar los cachés de serialización llamando a ObjectOutputStream.reset() o su proceso perderá memoria y eventualmente morirá.La desventaja es que los objetos no transitorios se volverán a serializar.¡Serializar objetos de resultados temporales puede ser un poco más complicado de lo que piensas!

Considere el uso de referencias suaves.Si no sabe qué son las referencias suaves, lea el javadoc para java.lang.ref.SoftReference

Manténgase alejado de las referencias fantasmas y las referencias débiles a menos que realmente se excite.

Finalmente, si realmente no puedes tolerar el GC, usa Realtime Java.

No, no estoy bromeando.

La implementación de referencia se puede descargar gratis y el libro de Peter Dibbles de SUN es una lectura realmente buena.

En lo que respecta a los finalizadores:

  1. Son prácticamente inútiles.No se garantiza que se convoquen de manera oportuna, o incluso que se convoquen en absoluto (si el GC nunca se ejecuta, tampoco lo harán los finalizadores).Esto significa que, por lo general, no deberías confiar en ellos.
  2. No se garantiza que los finalizadores sean idempotentes.El recolector de basura tiene mucho cuidado para garantizar que nunca llamará finalize() más de una vez sobre el mismo objeto.Con objetos bien escritos, no importará, pero con objetos mal escritos, llamar a finalize varias veces puede causar problemas (p. ej.doble liberación de un recurso nativo...chocar).
  3. Cada objeto que tiene un finalize() El método también debe proporcionar una close() (o similar) método.Esta es la función que deberías llamar.p.ej., FileInputStream.close().No hay razón para llamar finalize() cuando tienes un método más apropiado que es destinado a ser llamado por usted.

Suponiendo que los finalizadores son similares a su homónimo .NET, entonces solo necesita llamarlos cuando tenga recursos, como identificadores de archivos, que puedan filtrarse.La mayoría de las veces sus objetos no tienen estas referencias, por lo que no es necesario llamarlos.

Es malo intentar recoger la basura porque en realidad no es tu basura.Le ha dicho a la VM que asigne algo de memoria cuando creó objetos y el recolector de basura oculta información sobre esos objetos.Internamente, el GC realiza optimizaciones en las asignaciones de memoria que realiza.Cuando intentas recolectar la basura manualmente, no tienes conocimiento de lo que el GC quiere retener y de qué quiere deshacerse, simplemente estás forzando su mano.Como resultado, arruinas los cálculos internos.

Si supiera más sobre lo que el GC mantiene internamente, entonces podría tomar decisiones más informadas, pero entonces se habría perdido los beneficios del GC.

El verdadero problema al cerrar los identificadores del sistema operativo al finalizar es que los procesos de finalización se ejecutan sin un orden garantizado.Pero si tienes identificadores para las cosas que bloquean (piensa, por ej.sockets) potencialmente su código puede entrar en una situación de punto muerto (nada trivial).

Así que estoy a favor de cerrar explícitamente los identificadores de una manera ordenada y predecible.Básicamente, el código para tratar con recursos debe seguir el patrón:

SomeStream s = null;
...
try{
   s = openStream();
   ....
   s.io();
   ...
} finally {
   if (s != null) {
       s.close();
       s = null;
   }
}

Se vuelve aún más complicado si escribes tus propias clases que funcionan a través de JNI y abren identificadores.Debe asegurarse de que las manijas estén cerradas (liberadas) y que esto sucederá solo una vez.El identificador del sistema operativo que con frecuencia se pasa por alto en Desktop J2SE es Graphics[2D].Incluso BufferedImage.getGrpahics() potencialmente puede devolverle el identificador que apunta a un controlador de video (que en realidad contiene el recurso en la GPU).Si no lo libera usted mismo y deja que el recolector de basura haga el trabajo, puede encontrar OutOfMemory extraño y una situación similar en la que se quedó sin mapas de bits asignados en la tarjeta de video pero aún tiene mucha memoria.En mi experiencia, esto sucede con bastante frecuencia en bucles cerrados al trabajar con objetos gráficos (extraer miniaturas, escalar, enfocar, lo que sea).

Básicamente, GC no se hace cargo de la responsabilidad de los programadores de la correcta gestión de recursos.Sólo se ocupa de la memoria y nada más.La llamada Stream.finalize a close() en mi humilde opinión se implementaría mejor lanzando la excepción new RuntimeError ("recolección de basura de la secuencia que aún está abierta").Ahorrará horas y días de depuración y limpieza de código después de que los aficionados descuidados dejaran los extremos perdidos.

Feliz codificación.

Paz.

El GC optimiza mucho cuándo finalizar las cosas correctamente.

Entonces, a menos que esté familiarizado con cómo funciona realmente el GC y cómo etiqueta las generaciones, llamar manualmente a finalizar o iniciar el GC probablemente perjudicará el rendimiento en lugar de ayudar.

Evite los finalizadores.No hay garantía de que los llamen de manera oportuna.Podría pasar bastante tiempo antes de que el sistema de administración de memoria (es decir, el recolector de basura) decida recolectar un objeto con un finalizador.

Mucha gente usa finalizadores para hacer cosas como cerrar conexiones de socket o eliminar archivos temporales.Al hacerlo, hace que el comportamiento de su aplicación sea impredecible y esté vinculado a cuándo la JVM va a GC su objeto.Esto puede llevar a escenarios de "falta de memoria", no debido a que el montón de Java esté agotado, sino a que el sistema se esté quedando sin identificadores para un recurso en particular.

Otra cosa a tener en cuenta es que introducir llamadas a System.gc() o martillos similares puede mostrar buenos resultados en su entorno, pero no necesariamente se traducirán a otros sistemas.No todo el mundo ejecuta la misma JVM, hay muchas, SUN, IBM J9, BEA JRockit, Harmony, OpenJDK, etc…Todas estas JVM se ajustan al JCK (es decir, aquellas que han sido probadas oficialmente), pero tienen mucha libertad cuando se trata de hacer las cosas rápido.GC es una de esas áreas en las que todo el mundo invierte mucho.Usar un martillo muchas veces destruirá ese esfuerzo.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top