我们最近将消息处理应用程序从Java7升级到Java8。自升级以来,我们偶尔会发现一个异常,即在读取流时,流已被关闭。日志记录显示终结器线程正在对保存流的对象调用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()
只返回传入contstructor的输入流(请参见上面的代码块)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
对象在MimeBodyPart.WriteBodyPartContent
的堆栈框架中作为this
可以从当前线程的堆栈中访问。我不明白为什么JVM会调用finalize()
。
我尝试提取相关代码,并在自己的机器上以严密的循环运行它,但我无法重现问题。我们可以在一个开发服务器上以高负载可靠地重现问题,但任何创建较小的可重现测试用例的尝试都失败了。代码在Java7下编译,但在Java8下执行。如果我们切换回Java7而不重新编译,问题就不会发生。
作为解决办法,我使用Java Mail MIME库重写了受影响的代码,问题已经解决(可能Java Mail不使用finalize()
)。但是,我担心应用程序中的其他finalize()
方法可能被错误调用,或者Java试图垃圾收集仍在使用的对象。
我知道当前的最佳实践建议不要使用finalize()
,我可能会重新访问这个自制库,以删除finalize()
方法。也就是说,以前有人遇到过这个问题吗?有人对原因有什么想法吗?
这里有点猜想。即使在堆栈上的局部变量中有对对象的引用,即使在堆栈上有对该对象的实例方法的活动调用,也有可能对该对象进行最终确定和垃圾收集!要求对象不可达。即使它在堆栈上,如果没有后续代码触及该引用,它也可能无法访问。
有关如何在引用对象的局部变量仍在作用域内时对其进行GC的示例,请参见另一个答案。
下面是如何在实例方法调用处于活动状态时完成对象的示例:
class FinalizeThis {
protected void finalize() {
System.out.println("finalized!");
}
void loop() {
System.out.println("loop() called");
for (int i = 0; i < 1_000_000_000; i++) {
if (i % 1_000_000 == 0)
System.gc();
}
System.out.println("loop() returns");
}
public static void main(String[] args) {
new FinalizeThis().loop();
}
}
当loop()
方法处于活动状态时,任何代码都不可能使用对finalizeThis
对象的引用执行任何操作,因此它是不可访问的。因此,它可以最终确定和GC'。在JDK 8 GA上,它打印以下内容:
loop() called
finalized!
loop() returns
每一次。
MimeBodyPart
也可能发生类似的情况。它是否存储在局部变量中?(似乎是这样,因为代码似乎遵循了一个约定,即字段以M_
前缀命名。)
更新
在评论中,业务方案建议作出以下修改:
public static void main(String[] args) {
FinalizeThis finalizeThis = new FinalizeThis();
finalizeThis.loop();
}
有了这个变化,他没有观察到最终结果,我也没有。但是,如果做了这个进一步的变化:
public static void main(String[] args) {
FinalizeThis finalizeThis = new FinalizeThis();
for (int i = 0; i < 1_000_000; i++)
Thread.yield();
finalizeThis.loop();
}
问题内容: 我得到了这个,我期望它在打印x.withdraw()时能打印410。 这是我的代码: 我是否需要在类本身中修复某些问题,或者我的方法调用有问题? 问题答案: 您在实例上设置具有相同名称的属性: 您正在尝试调用的是该属性,而不是方法。Python不会区分方法和属性,它们也不位于单独的命名空间中。 为属性使用其他名称;(退出的过去时)作为更好的属性名称浮现在脑海: (我也纠正了一个错字;您
问题内容: 当我尝试在任何上下文中导入时,都会引发此错误: 知道我该如何解决吗?我只是通过打开Terminal,运行然后输入即可生成此错误。 问题答案: 昨天我遇到了同样的问题,没有安装Hashlib,尝试使用pip安装它会给出该错误。我通过使用easy_install进行安装来修复它。 另外我还必须在Windows上为Python 2.7安装Scipy和Microsoft Visual C ++
问题内容: 我在Django中创建URL视图时遇到问题。它给了我这个错误(ferrol是一个Space对象): 这是代码: spaces / models.py Main urls.py spaces / urls.py spaces / views.py 问题答案: 在你的spaces / urls.py文件中,你必须提供查看方法的完整路径: 或像这样:
我有一个对象列表(文件夹)。该列表中的每个对象A都有一个对象B(合作伙伴)列表,对象B也有一个对象C(人员)列表。对象C包含一个属性代码,我想用它来使用Java8进行过滤。 我试过下面的代码,但它似乎不工作: 你们知道我如何使用FlatMap从
问题内容: 我是Python的新手,并且正在学习教程。本教程中有一个示例: 现在,在教程中,。但就我而言,我得到以下错误: 问题答案: 好像你已经用指向类实例的相同名称遮盖了指向类的内置名称。这是一个例子: 我相信这是显而易见的。Python将对象名称(函数和类也是对象)存储在字典中(命名空间实现为字典),因此你可以在任何范围内重写几乎任何名称。它不会显示为某种错误。如你所知,Python强调“特
假设我有一个Foo对象列表。Foo上有一个属性,我必须使用它从数据源中获取Bar对象。然后我必须将每个bar对象映射回我得到bar的原始Foo对象。 我的尝试如下: