当前位置: 首页 > 知识库问答 >
问题:

在Java8中对强可达对象调用finalize()

袁弘方
2023-03-14

我们最近将消息处理应用程序从Java7升级到Java8。自升级以来,我们偶尔会发现一个异常,即在读取流时,流已被关闭。日志记录显示终结器线程正在对保存流的对象调用finalize()(这反过来会关闭流)。

守则的基本大纲如下:

MIMEWriter writer = new MIMEWriter( out );
in = new InflaterInputStream( databaseBlobInputStream );
MIMEBodyPart attachmentPart = new MIMEBodyPart( in );
writer.writePart( attachmentPart );

MimeWriterMimeBodyPart是自制的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的调用链中,如下所示:

  1. mimewriter.writePart()写入部件的标题,然后调用part.writebodyPartContent(this)
  2. mimebodypart.writebodypartcontent()调用我们的实用工具方法ioutil.copy(getContentStream(),out)将内容流式传输到输出
  3. mimebodypart.getContentStream()只返回传入contstructor的输入流(请参见上面的代码块)
  4. 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()方法。也就是说,以前有人遇到过这个问题吗?有人对原因有什么想法吗?

共有1个答案

叶衡虑
2023-03-14

这里有点猜想。即使在堆栈上的局部变量中有对对象的引用,即使在堆栈上有对该对象的实例方法的活动调用,也有可能对该对象进行最终确定和垃圾收集!要求对象不可达。即使它在堆栈上,如果没有后续代码触及该引用,它也可能无法访问。

有关如何在引用对象的局部变量仍在作用域内时对其进行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对象。 我的尝试如下: