Недавно мы обновили наше приложение обработки сообщений от Java 7 до Java 8. С момента обновления мы получаем случайное исключение, что поток был закрыт во время чтения. Ведение журнала показывает, что поток финализатора вызывает finalize()
объекта, который содержит поток (который, в свою очередь, закрывает поток).
Основной контур кода выглядит следующим образом:
MIMEWriter writer = new MIMEWriter( out );
in = new InflaterInputStream( databaseBlobInputStream );
MIMEBodyPart attachmentPart = new MIMEBodyPart( in );
writer.writePart( attachmentPart );
MIMEWriter
и MIMEBodyPart
являются частью домашней библиотеки MIME/HTTP. MIMEBodyPart
extends HTTPMessage
, который имеет следующее:
public void close() throws IOException
{
if ( m_stream != null )
{
m_stream.close();
}
}
protected void finalize()
{
try
{
close();
}
catch ( final Exception ignored ) { }
}
Исключение происходит в цепочке вызовов MIMEWriter.writePart
, которая выглядит следующим образом:
-
MIMEWriter.writePart()
записывает заголовки для части, затем вызываетpart.writeBodyPartContent( this )
-
MIMEBodyPart.writeBodyPartContent()
вызывает наш служебный методIOUtil.copy( getContentStream(), out )
для потоковой передачи содержимого на вывод -
MIMEBodyPart.getContentStream()
просто возвращает входной поток, переданный в контрструктор (см. блок кода выше) -
IOUtil.copy
имеет цикл, который считывает блок 8K из входного потока и записывает его в выходной поток до тех пор, пока входной поток не станет пустым.
Вызывается MIMEBodyPart.finalize()
во время выполнения IOUtil.copy
, и он получает следующее исключение:
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)
Мы помещаем некоторый журнал в метод HTTPMessage.close()
, который регистрировал трассировку стека вызывающего и доказал, что это определенно поток финализатора, вызывающий HTTPMessage.finalize()
, пока выполняется IOUtil.copy()
.
Объект MIMEBodyPart
определенно доступен из текущего стека потоков как this
в фрейме стека для MIMEBodyPart.writeBodyPartContent
. Я не понимаю, почему JVM будет называть finalize()
.
Я попытался извлечь соответствующий код и запустить его в узком цикле на моей собственной машине, но я не могу воспроизвести проблему. Мы можем надежно воспроизвести проблему с высокой нагрузкой на одном из наших серверов-разработчиков, но все попытки создать меньший воспроизводимый тестовый пример потерпели неудачу. Код компилируется под Java 7, но выполняется под Java 8. Если мы перейдем на Java 7 без перекомпиляции, проблема не возникает.
В качестве обходного пути я переписал затронутый код с помощью библиотеки электронной почты Java Mail MIME, и проблема исчезла (предположительно, Java Mail не использует finalize()
). Тем не менее, я обеспокоен тем, что другие методы finalize()
в приложении могут быть вызваны некорректно или что Java пытается уничтожить все объекты, которые все еще используются.
Я знаю, что действующая передовая практика рекомендует использовать finalize()
, и я, вероятно, вернусь к этой домашней библиотеке, чтобы удалить методы finalize()
. Говоря это, кто-нибудь сталкивался с этим вопросом раньше? Кто-нибудь имеет какие-либо идеи относительно причины?