我们最近将消息处理应用程序从 Java 7 升级到 Java 8。自升级以来,我们偶尔会遇到一个异常,即流在读取时已关闭。日志显示终结器线程正在调用finalize()
在保存流的对象上(进而关闭流)。
代码的基本轮廓如下:
MIMEWriter writer = new MIMEWriter( out );
in = new InflaterInputStream( databaseBlobInputStream );
MIMEBodyPart attachmentPart = new MIMEBodyPart( in );
writer.writePart( attachmentPart );
MIMEWriter
and MIMEBodyPart
是本土 MIME/HTTP 库的一部分。MIMEBodyPart
延伸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 块并将其写入输出流,直到输入流为空。
The 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()
while IOUtil.copy()
在跑。
The MIMEBodyPart
对象肯定可以从当前线程的堆栈访问,如下所示this
在堆栈帧中MIMEBodyPart.writeBodyPartContent
。我不明白为什么 JVM 会调用finalize()
.
我尝试提取相关代码并在我自己的机器上紧密循环运行它,但我无法重现该问题。我们可以在一台开发服务器上可靠地重现高负载的问题,但任何创建较小的可重现测试用例的尝试都失败了。代码在Java 7下编译,但在Java 8下执行。如果我们切换回Java 7而不重新编译,则不会出现该问题。
作为解决方法,我使用 Java Mail MIME 库重写了受影响的代码,问题已经消失(大概 Java Mail 不使用finalize()
)。然而,我担心其他finalize()
应用程序中的方法可能被错误调用,或者 Java 正在尝试对仍在使用的对象进行垃圾收集。
我知道当前的最佳实践建议不要使用finalize()
我可能会重新访问这个本土图书馆以删除finalize()
方法。话虽这么说,以前有人遇到过这个问题吗?有人对原因有任何想法吗?