为了账号安全,请及时绑定邮箱和手机立即绑定

在Java 8中调用强可达对象

在Java 8中调用强可达对象

米琪卡哇伊 2019-07-09 15:26:32
在Java 8中调用强可达对象最近,我们将消息处理应用程序从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()方法,该方法记录调用者的堆栈跟踪,并证明调用的绝对是终结器线程。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()方法。话虽如此,以前有人见过这个问题吗?有谁知道原因吗?
查看完整描述

3 回答

?
慕森王

TA贡献1777条经验 获得超3个赞

这里有点猜测。即使堆栈上的局部变量中有对象的引用,即使存在主动调用堆栈上该对象的实例方法!要求对象是达不到..即使它在堆栈上,如果后续代码没有接触到该引用,它也可能是不可访问的。

看见另一个答案例如,当引用对象的局部变量仍在作用域时,如何对象进行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();
    }

最后定稿再次发生。我怀疑原因是如果没有循环,main()方法是解释的,而不是编译的。对于可达性分析,解释器可能不太积极。在收益率循环就位后,main()方法被编译,JIT编译器检测到finalizeThis已变得不可及,而loop()方法正在执行。

触发此行为的另一种方法是使用-Xcomp选项,它强制在执行之前对方法进行JIT编译。我不会以这种方式运行整个应用程序-JIT-编译所有东西都会非常缓慢,占用大量的空间-但是它对于在小型测试程序中清除这种情况很有用,而不是修补循环。


查看完整回答
反对 回复 2019-07-09
?
蝴蝶刀刀

TA贡献1801条经验 获得超8个赞

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

查看完整回答
反对 回复 2019-07-09
  • 3 回答
  • 0 关注
  • 276 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信