Java 8 中强可达对象上的 Finalize() 调用

2023-12-03

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

代码的基本轮廓如下:

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

MIMEWriter and 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,如下:

  1. MIMEWriter.writePart()写入该部分的标题,然后调用part.writeBodyPartContent( this )
  2. MIMEBodyPart.writeBodyPartContent()调用我们的实用方法IOUtil.copy( getContentStream(), out )将内容流式传输到输出
  3. MIMEBodyPart.getContentStream()仅返回传递给构造函数的输入流(参见上面的代码块)
  4. IOUtil.copy有一个循环从输入流中读取 8K 块并将其写入输出流,直到输入流为空。

The 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() while IOUtil.copy()在跑。

The MIMEBodyPart对象肯定可以从当前线程的堆栈访问,如下所示this在堆栈帧中MIMEBodyPart.writeBodyPartContent。我不明白为什么 JVM 会调用finalize().

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

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

我知道当前的最佳实践建议不要使用finalize()我可能会重新访问这个本土图书馆以删除finalize()方法。话虽这么说,以前有人遇到过这个问题吗?有人对原因有任何想法吗?


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

See 这个另一个答案有关如何在引用对象的局部变量仍在范围内时对对象进行 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_字首。)

UPDATE

在评论中,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();
    }

最终确定再次发生。我怀疑原因是没有循环,main()方法是解释的,而不是编译的。解释器对于可达性分析可能不太积极。随着产量循环的到位,main()方法被编译,并且 JIT 编译器检测到finalizeThis已变得无法访问,而loop()方法正在执行。

触发此行为的另一种方法是使用-XcompJVM 的选项,强制方法在执行前进行 JIT 编译。我不会以这种方式运行整个应用程序——即时编译所有内容可能会非常慢并且占用大量空间——但它对于在小测试程序中清除此类情况非常有用,而不是修补循环。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java 8 中强可达对象上的 Finalize() 调用 的相关文章

  • 如何为最终用户方便地启动Java GUI程序

    用户想要从以下位置启动 Java GUI 应用程序Windows 以及一些额外的 JVM 参数 例如 javaw Djava util logging config file logging properties jar MyGUI jar
  • Java new Date() 打印

    刚刚学习 Java 我知道这可能听起来很愚蠢 但我不得不问 System out print new Date 我知道参数中的任何内容都会转换为字符串 最终值是 new Date 返回对 Date 对象的引用 那么它是如何打印这个的呢 Mo
  • Java Swing:从 JOptionPane 获取文本值

    我想创建一个用于 POS 系统的新窗口 用户输入的是客户拥有的金额 并且窗口必须显示兑换金额 我是新来的JOptionPane功能 我一直在使用JAVAFX并且它是不同的 这是我的代码 public static void main Str
  • Spring Batch 多线程 - 如何使每个线程读取唯一的记录?

    这个问题在很多论坛上都被问过很多次了 但我没有看到适合我的答案 我正在尝试在我的 Spring Batch 实现中实现多线程步骤 有一个包含 100k 条记录的临时表 想要在 10 个线程中处理它 每个线程的提交间隔为 300 因此在任何时
  • Java中反射是如何实现的?

    Java 7 语言规范很早就指出 本规范没有详细描述反射 我只是想知道 反射在Java中是如何实现的 我不是问它是如何使用的 我知道可能没有我正在寻找的具体答案 但任何信息将不胜感激 我在 Stackoverflow 上发现了这个 关于 C
  • Java EE:如何获取我的应用程序的 URL?

    在 Java EE 中 如何动态检索应用程序的完整 URL 例如 如果 URL 是 localhost 8080 myapplication 我想要一个可以简单地将其作为字符串或其他形式返回给我的方法 我正在运行 GlassFish 作为应
  • 在 java 类和 android 活动之间传输时音频不清晰

    我有一个android活动 它连接到一个java类并以套接字的形式向它发送数据包 该类接收声音数据包并将它们扔到 PC 扬声器 该代码运行良好 但在 PC 扬声器中播放声音时会出现持续的抖动 中断 安卓活动 public class Sen
  • Java JDBC:更改表

    我希望对此表进行以下修改 添加 状态列 varchar 20 日期列 时间戳 我不确定该怎么做 String createTable Create table aircraft aircraftNumber int airLineCompa
  • 我可以使用 HSQLDB 进行 junit 测试克隆 mySQL 数据库吗

    我正在开发一个 spring webflow 项目 我想我可以使用 HSQLDB 而不是 mysql 进行 junit 测试吗 如何将我的 mysql 数据库克隆到 HSQLDB 如果您使用 spring 3 1 或更高版本 您可以使用 s
  • 从 127.0.0.1 到 2130706433,然后再返回

    使用标准 Java 库 从 IPV4 地址的点分字符串表示形式获取的最快方法是什么 127 0 0 1 到等效的整数表示 2130706433 相应地 反转所述操作的最快方法是什么 从整数开始2130706433到字符串表示形式 127 0
  • 为什么HashMap不能保证map的顺序随着时间的推移保持不变

    我在这里阅读有关 Hashmap 和 Hashtable 之间的区别 http javarevisited blogspot sg 2010 10 difference Between hashmap and html http javar
  • 使用Caliper时如何指定命令行?

    我发现 Google 的微型基准测试项目 Caliper 非常有趣 但文档仍然 除了一些示例 完全不存在 我有两种不同的情况 需要影响 JVM Caliper 启动的命令行 我需要设置一些固定 最好在几个固定值之间交替 D 参数 我需要指定
  • Java Integer CompareTo() - 为什么使用比较与减法?

    我发现java lang Integer实施compareTo方法如下 public int compareTo Integer anotherInteger int thisVal this value int anotherVal an
  • 如何在 javadoc 中使用“<”和“>”而不进行格式化?

    如果我写
  • AWS 无法从 START_OBJECT 中反序列化 java.lang.String 实例

    我创建了一个 Lambda 函数 我想在 API 网关的帮助下通过 URL 访问它 我已经把一切都设置好了 我还创建了一个application jsonAPI Gateway 中的正文映射模板如下所示 input input params
  • 编译器抱怨“缺少返回语句”,即使不可能达到缺少返回语句的条件

    在下面的方法中 编译器抱怨缺少退货声明即使该方法只有一条路径 并且它包含一个return陈述 抑制错误需要另一个return陈述 public int foo if true return 5 鉴于Java编译器可以识别无限循环 https
  • 有没有办法为Java的字符集名称添加别名

    我收到一个异常 埋藏在第 3 方库中 消息如下 java io UnsupportedEncodingException BIG 5 我认为发生这种情况是因为 Java 没有定义这个名称java nio charset Charset Ch
  • JGit 检查分支是否已签出

    我正在使用 JGit 开发一个项目 我设法删除了一个分支 但我还想检查该分支是否已签出 我发现了一个变量CheckoutCommand但它是私有的 private boolean isCheckoutIndex return startCo
  • 如何实现仅当可用内存较低时才将数据交换到磁盘的写缓存

    我想将应用程序生成的数据缓存在内存中 但如果内存变得稀缺 我想将数据交换到磁盘 理想情况下 我希望虚拟机通知它需要内存并将我的数据写入磁盘并以这种方式释放一些内存 但我没有看到任何方法以通知我的方式将自己挂接到虚拟机中before an O
  • 节拍匹配算法

    我最近开始尝试创建一个移动应用程序 iOS Android 它将自动击败比赛 http en wikipedia org wiki Beatmatching http en wikipedia org wiki Beatmatching 两

随机推荐