when method funcion example collector called java garbage-collection java-8 finalizer finalize

method - Finalizar() llamado objeto fuertemente accesible en Java 8



public void finalize() throws throwable (2)

Tu finalizador no es correcto

En primer lugar, no necesita el bloque catch, y debe llamar a super.finalize() en su propio bloque finally{} . La forma canónica de un finalizador es la siguiente:

protected void finalize() throws Throwable { try { // do stuff } finally { super.finalize(); } }

En segundo lugar, m_stream que tienes la única referencia a m_stream , que puede o no ser correcta. El miembro m_stream debe finalizar por sí mismo. Pero no necesitas hacer nada para lograrlo. En m_stream instancia, m_stream será un FileInputStream o FileOutputStream o un flujo de socket, y ya se finalizarán correctamente.

Yo sólo lo eliminaría.

Recientemente, actualizamos nuestra aplicación de procesamiento de mensajes de Java 7 a Java 8. Desde la actualización, recibimos una excepción ocasional de que una secuencia se ha cerrado mientras se está leyendo. El registro muestra que el subproceso finalizador está llamando a finalize() en el objeto que contiene el flujo (que a su vez cierra el flujo).

El esquema básico del código es el siguiente:

MIMEWriter writer = new MIMEWriter( out ); in = new InflaterInputStream( databaseBlobInputStream ); MIMEBodyPart attachmentPart = new MIMEBodyPart( in ); writer.writePart( attachmentPart );

MIMEWriter y MIMEBodyPart forman parte de una biblioteca MIME / HTTP propia. MIMEBodyPart extiende HTTPMessage , que tiene lo siguiente:

public void close() throws IOException { if ( m_stream != null ) { m_stream.close(); } } protected void finalize() { try { close(); } catch ( final Exception ignored ) { } }

La excepción se produce en la cadena de invocación de MIMEWriter.writePart , que es la siguiente:

  1. MIMEWriter.writePart() escribe los encabezados de la parte y luego llama a part.writeBodyPartContent( this )
  2. MIMEBodyPart.writeBodyPartContent() llama a nuestro método de utilidad IOUtil.copy( getContentStream(), out ) para transmitir el contenido a la salida
  3. MIMEBodyPart.getContentStream() simplemente devuelve la secuencia de entrada que se pasa al contstructor (consulte el bloque de código anterior)
  4. IOUtil.copy tiene un bucle que lee un fragmento de 8K de la secuencia de entrada y lo escribe en la secuencia de salida hasta que la secuencia de entrada esté vacía.

Se MIMEBodyPart.finalize() mientras IOUtil.copy está ejecutando, y obtiene la siguiente excepción:

java.io.IOException: Stream closed at java.util.zip.InflaterInputStream.ensureOpen(InflaterInputStream.java:67) at java.util.zip.InflaterInputStream.read(InflaterInputStream.java:142) at java.io.FilterInputStream.read(FilterInputStream.java:107) at com.blah.util.IOUtil.copy(IOUtil.java:153) at com.blah.core.net.MIMEBodyPart.writeBodyPartContent(MIMEBodyPart.java:75) at com.blah.core.net.MIMEWriter.writePart(MIMEWriter.java:65)

Pusimos algo de registro en el método HTTPMessage.close() que registró el seguimiento de la pila del llamante y demostró que es definitivamente el subproceso finalizador que invoca a HTTPMessage.finalize() mientras IOUtil.copy() está ejecutando.

El objeto MIMEBodyPart es definitivamente accesible desde la pila del hilo actual como this en el marco de la pila para MIMEBodyPart.writeBodyPartContent . No entiendo por qué la JVM llamaría finalize() .

Intenté extraer el código relevante y ejecutarlo en un circuito cerrado en mi propia máquina, pero no puedo reproducir el problema. Podemos reproducir de manera confiable el problema con alta carga en uno de nuestros servidores de desarrollo, pero cualquier intento de crear un caso de prueba reproducible más pequeño ha fallado. El código se compila en Java 7 pero se ejecuta en Java 8. Si volvemos a Java 7 sin volver a compilar, el problema no se produce.

Como solución alternativa, reescribí el código afectado usando la biblioteca MIME de Java Mail y el problema desapareció (probablemente Java Mail no usa finalize() ). Sin embargo, me preocupa que otros métodos finalize() en la aplicación puedan llamarse incorrectamente, o que Java esté tratando de recolectar objetos que aún están en uso.

Sé que las mejores prácticas actuales recomiendan no usar finalize() y probablemente volveré a visitar esta biblioteca local para eliminar los métodos finalize() . Dicho esto, ¿alguien ha encontrado este problema antes? ¿Alguien tiene alguna idea en cuanto a la causa?


Un poco de conjetura aquí. Es posible que un objeto se finalice y se recolecte basura incluso si hay referencias a él en las variables locales en la pila, ¡e incluso si hay una llamada activa a un método de instancia de ese objeto en la pila! El requisito es que el objeto sea inalcanzable . Incluso si está en la pila, si ningún código posterior toca esa referencia, es potencialmente inalcanzable.

Vea esta otra respuesta para ver un ejemplo de cómo un objeto puede ser GC''ed mientras una variable local que hace referencia todavía está dentro del alcance.

Este es un ejemplo de cómo se puede finalizar un objeto mientras una llamada de método de instancia está activa:

class FinalizeThis { protected void finalize() { System.out.println("finalized!"); } void loop() { System.out.println("loop() called"); for (int i = 0; i < 1_000_000_000; i++) { if (i % 1_000_000 == 0) System.gc(); } System.out.println("loop() returns"); } public static void main(String[] args) { new FinalizeThis().loop(); } }

Si bien el método loop() está activo, no hay posibilidad de que ningún código haga nada con la referencia al objeto FinalizeThis , por lo que es inalcanzable. Y por lo tanto puede ser finalizado y GC''ed. En JDK 8 GA, esto imprime lo siguiente:

loop() called finalized! loop() returns

cada vez.

Algo similar podría estar ocurriendo con MimeBodyPart . ¿Está siendo almacenado en una variable local? (Parece que sí, ya que el código parece adherirse a una convención de que los campos se nombran con un prefijo m_ ).

ACTUALIZAR

En los comentarios, el OP sugirió hacer el siguiente cambio:

public static void main(String[] args) { FinalizeThis finalizeThis = new FinalizeThis(); finalizeThis.loop(); }

Con este cambio no observó la finalización, y yo tampoco. Sin embargo, si se realiza este cambio adicional:

public static void main(String[] args) { FinalizeThis finalizeThis = new FinalizeThis(); for (int i = 0; i < 1_000_000; i++) Thread.yield(); finalizeThis.loop(); }

La finalización una vez más se produce. Sospecho que la razón es que sin el bucle, el método main() se interpreta, no se compila. El intérprete es probablemente menos agresivo en el análisis de accesibilidad. Con el bucle de rendimiento en su lugar, el método main() se compila, y el compilador JIT detecta que finalizeThis ha vuelto inalcanzable mientras se ejecuta el método loop() .

Otra forma de desencadenar este comportamiento es usar la opción -Xcomp para la JVM, lo que obliga a los métodos a compilarse JIT antes de la ejecución. No ejecutaría una aplicación completa de esta manera (la compilación JIT de todo puede ser bastante lenta y ocupar mucho espacio), pero es útil para eliminar casos como este en pequeños programas de prueba, en lugar de jugar con los bucles.