我们最近将消息处理应用程序从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
扩展了HTTPMessage
,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块并将其写入输出流,直到输入流为空。
IOUtil.copy
正在运行时,调用MIMEBodyPart.finalize()
会收到以下异常: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()
方法中添加了一些日志,记录了调用者的堆栈跟踪,并证明正在运行 IOUtil.copy()
时肯定是终结器线程调用 HTTPMessage.finalize()
。
MIMEBodyPart
对象在当前线程的堆栈中作为 this
存在于 MIMEBodyPart.writeBodyPartContent
的堆栈帧中。我不明白为什么 JVM 会调用 finalize()
。
我尝试将相关代码提取出来,在自己的机器上进行紧密循环运行,但无法复现该问题。我们可以在其中一个开发服务器上高负载下可靠地复现该问题,但是任何尝试创建较小的可重现测试用例的尝试均失败。代码在 Java 7 下编译,但在 Java 8 下执行。如果我们切换回 Java 7 而没有重新编译,则不会发生此问题。
作为一种解决方法,我已经使用 Java Mail MIME 库重写了受影响的代码,并且问题已经消失了(可能是因为 Java Mail 没有使用 finalize()
)。但是,我担心应用程序中的其他 finalize()
方法可能被错误地调用,或者 Java 正在尝试回收仍在使用的对象。
我知道当前最佳实践不建议使用 finalize()
,我可能会重新审视这个自制库,以删除 finalize()
方法。话虽如此,有人遇到过这个问题吗?有没有人对原因有任何想法?
-XX:+PrintCompilation
来尝试查看问题的发生是否与某个方法的JIT编译对齐吗? - NathanMIMEBodyPart
可以使用给定的流in
。 - Nathanfinalize
方法已于Java 9中被弃用。请参阅问题为什么Java 9中废弃了finalize()方法?。 - Basil Bourque