Недавно мы обновили наше приложение обработки сообщений от 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(). Говоря это, кто-нибудь сталкивался с этим вопросом раньше? Кто-нибудь имеет какие-либо идеи относительно причины?
