Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/378.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/kotlin/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
在Java 8中对强可访问对象调用finalize()_Java_Garbage Collection_Java 8_Finalizer_Finalize - Fatal编程技术网

在Java 8中对强可访问对象调用finalize()

在Java 8中对强可访问对象调用finalize(),java,garbage-collection,java-8,finalizer,finalize,Java,Garbage Collection,Java 8,Finalizer,Finalize,我们最近将消息处理应用程序从Java7升级到Java8。自升级以来,我们偶尔会遇到一个异常,即在读取流时,该流已关闭。日志记录显示终结器线程正在对保存流的对象调用finalize()(这反过来会关闭流) 守则的基本内容如下: MIMEWriter writer = new MIMEWriter( out ); in = new InflaterInputStream( databaseBlobInputStream ); MIMEBodyPart attachmentPart = new MIM

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

守则的基本内容如下:

MIMEWriter writer = new MIMEWriter( out );
in = new InflaterInputStream( databaseBlobInputStream );
MIMEBodyPart attachmentPart = new MIMEBodyPart( in );
writer.writePart( attachmentPart );
protected void finalize() throws Throwable
{
    try
    {
        // do stuff
    }
    finally
    {
        super.finalize();
    }
}
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 ) { }
}
loop() called
finalized!
loop() returns
异常发生在
MIMEWriter.writePart
的调用链中,如下所示:

MIMEWriter writer = new MIMEWriter( out );
in = new InflaterInputStream( databaseBlobInputStream );
MIMEBodyPart attachmentPart = new MIMEBodyPart( in );
writer.writePart( attachmentPart );
protected void finalize() throws Throwable
{
    try
    {
        // do stuff
    }
    finally
    {
        super.finalize();
    }
}
  • 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.writeBodyPartContent
    的堆栈框架中,从当前线程的堆栈中肯定可以访问
    MIMEBodyPart
    对象,即
    this
    。我不明白为什么JVM会调用
    finalize()

    我试图提取相关代码并在自己的机器上以紧密循环的方式运行它,但我无法重现这个问题。我们可以在一个开发服务器上以高负载可靠地重现问题,但是任何创建较小的可重现测试用例的尝试都失败了。代码在Java7下编译,但在Java8下执行。如果我们在不重新编译的情况下切换回Java7,那么问题就不会发生

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


    我知道当前的最佳实践建议不要使用
    finalize()
    ,我可能会重新访问这个自行开发的库来删除
    finalize()
    方法。也就是说,以前有人遇到过这个问题吗?有人知道原因吗?

    您的终结器不正确

    首先,它不需要catch块,它必须在自己的
    finally{}
    块中调用
    super.finalize()
    。终结器的标准形式如下所示:

    MIMEWriter writer = new MIMEWriter( out );
    in = new InflaterInputStream( databaseBlobInputStream );
    MIMEBodyPart attachmentPart = new MIMEBodyPart( in );
    writer.writePart( attachmentPart );
    
    protected void finalize() throws Throwable
    {
        try
        {
            // do stuff
        }
        finally
        {
            super.finalize();
        }
    }
    
    其次,您假设您持有对
    m_stream
    的唯一引用,这可能是正确的,也可能是不正确的。
    m_流
    成员应自行完成。但你不需要做任何事情来实现这一点。最终,
    m_流
    将是一个
    FileInputStream
    FileOutputStream
    或套接字流,并且它们已经正确地完成了自己的定稿


    我想把它去掉。

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

    有关如何在引用对象的局部变量仍在作用域中时对其进行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();
        }
    }
    
    每一次

    MimeBodyPart
    也可能发生类似的情况。它是否存储在局部变量中?(看起来是这样的,因为代码似乎遵守了一个惯例,即字段的命名带有
    m
    前缀。)

    更新

    在评论中,OP建议进行以下更改:

        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();
        }
    
    publicstaticvoidmain(字符串[]args){
    FinalizeThis FinalizeThis=新的FinalizeThis();
    对于(int i=0;i<1_000;i++)
    螺纹屈服强度();
    finalizeThis.loop();
    }
    
    最终确定再次发生。我怀疑原因是没有循环,
    main()
    方法是解释的,而不是编译的。解释器在可达性分析方面可能不那么积极。使用yield循环,编译
    main()
    方法,JIT编译器检测到
    finalizeThis
    在执行
    loop()
    方法时变得不可访问


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

    如果没有其他解释,我会感到惊讶。当前线程始终为空