【发布时间】:2014-12-25 20:29:48
【问题描述】:
我们最近将消息处理应用程序从 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,具有以下内容:
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() 方法中添加了一些日志记录,该方法记录了调用者的堆栈跟踪,并证明在 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() 方法。话虽这么说,以前有人遇到过这个问题吗?有人对原因有任何想法吗?
【问题讨论】:
-
如果没有其他解释,我会感到惊讶。当前线程始终是收集器识别活动对象的“根”。您如何确定在您的 IOUtils.copy() 返回之前 调用了终结器?
-
这听起来很像 JIT 错误。我会在打开 JIT 调试的情况下运行,看看那里是否有任何模式。
-
@Bhaskar,异常证明
IOUtil.copy()正在执行时流已关闭。 -
@chrylis,您建议打开哪些标志?是否只是
-XX:+PrintCompilation尝试查看问题的发生是否与其中一种方法的 JIT 编译一致? -
是否有第二个无法访问的
MIMEBodyPart持有对同一个 m_stream 对象的引用?
标签: java garbage-collection java-8 finalizer finalize