Abbiamo recentemente aggiornato la nostra applicazione di elaborazione dei messaggi da Java 7 a Java 8. Dall'aggiornamento, si ottiene un'eccezione occasionale che un flusso è stato chiuso durante la lettura. La registrazione mostra che il thread del finalizzatore chiama finalize()
sull'oggetto che contiene lo stream (che a sua volta chiude lo stream).finalize() chiamato su oggetto fortemente raggiungibile in Java 8
Lo schema di base del codice è il seguente:
MIMEWriter writer = new MIMEWriter(out);
in = new InflaterInputStream(databaseBlobInputStream);
MIMEBodyPart attachmentPart = new MIMEBodyPart(in);
writer.writePart(attachmentPart);
MIMEWriter
e MIMEBodyPart
sono parte di una libreria/HTTP MIME home-grown. MIMEBodyPart
estende HTTPMessage
, che ha la seguente:
public void close() throws IOException
{
if (m_stream != null)
{
m_stream.close();
}
}
protected void finalize()
{
try
{
close();
}
catch (final Exception ignored) { }
}
L'eccezione si verifica nella catena invocazione di MIMEWriter.writePart
, che è la seguente:
MIMEWriter.writePart()
scrive le intestazioni per la parte, poi chiamapart.writeBodyPartContent(this)
MIMEBodyPart.writeBodyPartContent()
chiama il nostro metodo di utilitàIOUtil.copy(getContentStream(), out)
per lo streaming del contenuto nell'outputMIMEBodyPart.getContentStream()
jus t restituisce il flusso di input passato al contstructor (vedere il blocco di codice sopra)IOUtil.copy
ha un ciclo che legge un blocco di 8K dal flusso di input e lo scrive nel flusso di output finché il flusso di input non è vuoto.
Il MIMEBodyPart.finalize()
viene chiamato mentre IOUtil.copy
è in esecuzione, e si ottiene la seguente eccezione:
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)
Abbiamo messo un po 'di registrazione nel metodo HTTPMessage.close()
che ha registrato la traccia dello stack del chiamante e ha dimostrato che si tratta di sicuramente il thread di finalizzazione che sta invocando HTTPMessage.finalize()
mentre IOUtil.copy()
è in esecuzione.
L'oggetto MIMEBodyPart
è definitivamente raggiungibile dallo stack del thread corrente come this
nello stack frame per MIMEBodyPart.writeBodyPartContent
. Non capisco perché la JVM chiamerebbe finalize()
.
Ho tentato di estrarre il codice relativo e di eseguirlo in un ciclo chiuso sulla mia macchina, ma non riesco a riprodurre il problema. Possiamo riprodurre in modo affidabile il problema con un carico elevato su uno dei nostri server di sviluppo, ma qualsiasi tentativo di creare un caso di test riproducibile più piccolo ha avuto esito negativo. Il codice è compilato sotto Java 7 ma viene eseguito sotto Java 8. Se si passa a Java 7 senza ricompilare, il problema non si verifica.
Come soluzione temporanea, ho riscritto il codice interessato utilizzando la libreria MIME di Java Mail e il problema è scomparso (presumibilmente Java Mail non utilizza finalize()
). Tuttavia, sono preoccupato che altri metodi finalize()
nell'applicazione possano essere chiamati in modo errato o che Java stia cercando di raccogliere gli oggetti che sono ancora in uso.
So che l'attuale best practice consiglia di non utilizzare finalize()
e probabilmente esaminerò questa libreria cresciuta in proprio per rimuovere i metodi finalize()
. Detto questo, qualcuno ha mai incontrato prima questo problema? Qualcuno ha qualche idea sulla causa?
Sarei sorpreso se non ci fosse altra spiegazione a questo. Il thread corrente è sempre una "radice" per il collector per identificare gli oggetti live. Come sai per certo che il finalizzatore è stato invocato * prima * il tuo IOUtils.copy() restituisce? – Bhaskar
Sembra molto simile a un bug JIT. Vorrei correre con il debug JIT attivato e vedere se ci sono dei pattern lì. – chrylis
@Bhaskar, l'eccezione dimostra che il flusso viene chiuso mentre è in esecuzione 'IOUtil.copy()'. – Nathan