猿问

在Java 8中调用强可达对象

在Java 8中调用强可达对象

最近,我们将消息处理应用程序从Java 7升级到Java 8。自升级以来,偶尔会出现一个例外,即在读取流时,流已经关闭。日志记录显示终结器线程正在调用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()

    只返回传入构造器的输入流(请参阅上面的代码块)
  4. 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()方法,该方法记录调用者的堆栈跟踪,并证明调用的绝对是终结器线程。HTTPMessage.finalize()IOUtil.copy()正在奔跑。

这个MIMEBodyPart对象绝对可以从当前线程的堆栈中访问,如this在堆栈框架中MIMEBodyPart.writeBodyPartContent..我不明白为什么JVM会调用finalize().

我试着提取相关的代码并在我自己的机器上紧密地循环运行,但是我无法重现这个问题。我们可以在开发服务器上以高负载可靠地再现这个问题,但是任何创建更小的可重复测试用例的尝试都失败了。代码在Java 7下编译,在Java 8下执行。如果我们切换回Java 7而不重新编译,问题就不会发生。

作为解决办法,我已经使用JavaMail MIME库重写了受影响的代码,问题已经解决了(大概Java Mail不使用finalize())。然而,我担心的是finalize()应用程序中的方法可能被错误调用,或者Java试图垃圾收集仍在使用的对象。

我知道目前的最佳实践建议不要使用finalize()我可能会重新访问这个本地的库,以删除finalize()方法。话虽如此,以前有人见过这个问题吗?有谁知道原因吗?


米琪卡哇伊
浏览 281回答 3
3回答

慕森王

这里有点猜测。即使堆栈上的局部变量中有对象的引用,即使存在主动调用堆栈上该对象的实例方法!要求对象是达不到..即使它在堆栈上,如果后续代码没有接触到该引用,它也可能是不可访问的。看见另一个答案例如,当引用对象的局部变量仍在作用域时,如何对象进行GC‘编辑。下面是一个示例,说明如何在实例方法调用处于活动状态时最终确定对象:class&nbsp;FinalizeThis&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;protected&nbsp;void&nbsp;finalize()&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println("finalized!"); &nbsp;&nbsp;&nbsp;&nbsp;} &nbsp;&nbsp;&nbsp;&nbsp;void&nbsp;loop()&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println("loop()&nbsp;called"); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for&nbsp;(int&nbsp;i&nbsp;=&nbsp;0;&nbsp;i&nbsp;<&nbsp;1_000_000_000;&nbsp;i++)&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(i&nbsp;%&nbsp;1_000_000&nbsp;==&nbsp;0) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.gc(); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println("loop()&nbsp;returns"); &nbsp;&nbsp;&nbsp;&nbsp;} &nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;static&nbsp;void&nbsp;main(String[]&nbsp;args)&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;new&nbsp;FinalizeThis().loop(); &nbsp;&nbsp;&nbsp;&nbsp;}}而loop()方法是活动的,任何代码都不可能使用引用FinalizeThis对象,所以它是不可触及的。因此,它可以最后确定和GC‘。在JDK 8 GA上,这将打印以下内容:loop()&nbsp;called finalized!loop()&nbsp;returns每次。可能会发生类似的事情MimeBodyPart..它是否存储在局部变量中?(看起来是这样的,因为代码似乎遵循一种惯例,即字段以m_(前缀)更新在评论中,“任择议定书”建议作以下修改:&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;static&nbsp;void&nbsp;main(String[]&nbsp;args)&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;FinalizeThis&nbsp;finalizeThis&nbsp;=&nbsp;new&nbsp;FinalizeThis(); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;finalizeThis.loop(); &nbsp;&nbsp;&nbsp;&nbsp;}对于这一变化,他没有观察到最后定稿,我也没有观察到。然而,如果做了进一步的更改:&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;static&nbsp;void&nbsp;main(String[]&nbsp;args)&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;FinalizeThis&nbsp;finalizeThis&nbsp;=&nbsp;new&nbsp;FinalizeThis(); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for&nbsp;(int&nbsp;i&nbsp;=&nbsp;0;&nbsp;i&nbsp;<&nbsp;1_000_000;&nbsp;i++) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Thread.yield(); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;finalizeThis.loop(); &nbsp;&nbsp;&nbsp;&nbsp;}最后定稿再次发生。我怀疑原因是如果没有循环,main()方法是解释的,而不是编译的。对于可达性分析,解释器可能不太积极。在收益率循环就位后,main()方法被编译,JIT编译器检测到finalizeThis已变得不可及,而loop()方法正在执行。触发此行为的另一种方法是使用-Xcomp选项,它强制在执行之前对方法进行JIT编译。我不会以这种方式运行整个应用程序-JIT-编译所有东西都会非常缓慢,占用大量的空间-但是它对于在小型测试程序中清除这种情况很有用,而不是修补循环。

蝴蝶刀刀

我原则上同意打电话super.finalize()但在这种情况下HTTPMessage延展Object它有一个空的finalize()..我同意目前的代码不是最优的,但我也不确定这是否相关。问题是finalize()在Java 8下调用似乎是错误的,但在Java 7下却不是。
随时随地看视频慕课网APP

相关分类

Java
我要回答