Wir haben unsere Nachrichtenverarbeitungsanwendung kürzlich von Java 7 auf Java 8 aktualisiert. Seit dem Upgrade erhalten wir gelegentlich die Ausnahme, dass ein Stream geschlossen wurde, während er gelesen wird. Die Protokollierung zeigt, dass der Finalizer-Thread finalize()
für das Objekt aufruft, das den Stream enthält (wodurch wiederum der Stream geschlossen wird).finalize() hat stark erreichbares Objekt in Java aufgerufen 8
Die Grundzüge des Codes ist wie folgt:
MIMEWriter writer = new MIMEWriter(out);
in = new InflaterInputStream(databaseBlobInputStream);
MIMEBodyPart attachmentPart = new MIMEBodyPart(in);
writer.writePart(attachmentPart);
MIMEWriter
und MIMEBodyPart
Teil eines home-grown MIME/HTTP-Bibliothek sind. MIMEBodyPart
erstreckt HTTPMessage
, welches die folgenden:
public void close() throws IOException
{
if (m_stream != null)
{
m_stream.close();
}
}
protected void finalize()
{
try
{
close();
}
catch (final Exception ignored) { }
}
Die Ausnahme tritt in der Aufrufkette MIMEWriter.writePart
, die wie folgt lautet:
MIMEWriter.writePart()
die Header für den Teil schreibt, dann ruftpart.writeBodyPartContent(this)
MIMEBodyPart.writeBodyPartContent()
ruft unsere HilfsmethodeIOUtil.copy(getContentStream(), out)
auf, um den Inhalt zum Ausgang zu sendenMIMEBodyPart.getContentStream()
jus t gibt den an den contstructor übergebenen Eingabestrom zurück (siehe obigen Codeblock)IOUtil.copy
verfügt über eine Schleife, die einen 8K Chunk aus dem Eingabestream liest und in den Ausgabestream schreibt, bis der Eingabestream leer ist.
Die MIMEBodyPart.finalize()
heißt während IOUtil.copy
ausgeführt wird, und es wird die folgende Ausnahme:
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)
Wir einige Protokollierung setzen in der HTTPMessage.close()
Methode, die den Stack-Trace des Anrufers aufgezeichnet und bewiesen, dass es definitiv der Finalizer-Thread, der HTTPMessage.finalize()
aufruft, während IOUtil.copy()
läuft.
Das MIMEBodyPart
Objekt ist definitiv vom Stack des aktuellen Threads als this
im Stack-Frame für MIMEBodyPart.writeBodyPartContent
erreichbar. Ich verstehe nicht, warum die JVM finalize()
anrufen würde.
Ich habe versucht, den relevanten Code zu extrahieren und es in einer engen Schleife auf meinem eigenen Rechner laufen zu lassen, aber ich kann das Problem nicht reproduzieren. Wir können das Problem mit hoher Last auf einem unserer Dev-Server zuverlässig reproduzieren, aber alle Versuche, einen kleineren reproduzierbaren Testfall zu erstellen, sind fehlgeschlagen. Der Code wird unter Java 7 kompiliert, aber unter Java 8 ausgeführt. Wenn wir ohne Neukompilierung zurück zu Java 7 wechseln, tritt das Problem nicht auf.
Als Workaround habe ich den betroffenen Code mit der Java Mail MIME-Bibliothek neu geschrieben und das Problem ist weg (vermutlich verwendet Java Mail nicht finalize()
). Ich bin jedoch besorgt, dass andere Methoden in der Anwendung finalize()
möglicherweise falsch aufgerufen werden, oder dass Java versucht, Objekte zu sammeln, die noch verwendet werden.
Ich weiß, dass aktuelle Best Practice empfiehlt gegen die Verwendung finalize()
und ich werde wahrscheinlich diese selbstgewachsene Bibliothek wieder zu entfernen, um die finalize()
Methoden zu entfernen. Hat jemand schon einmal auf dieses Problem gestoßen? Hat jemand irgendwelche Ideen bezüglich der Ursache?
Ich wäre überrascht, wenn es keine andere Erklärung dafür gibt. Der aktuelle Thread ist immer eine "Wurzel" für den Kollektor, um Live-Objekte zu identifizieren. Wie können Sie sicher sein, dass der Finalizer aufgerufen wird * bevor * Ihre IOUtils.copy() zurückgibt? – Bhaskar
Das klingt sehr nach einem JIT-Bug. Ich würde laufen mit JIT-Debugging eingeschaltet und sehen, ob dort irgendein Muster ist. – chrylis
@Bhaskar, die Ausnahme beweist, dass der Stream geschlossen ist, während 'IOUtil.copy()' ausgeführt wird. – Nathan