我们最近将我们的消息处理应用程序从Java 7升级到Java 8.自升级以来,我们偶尔会遇到一个流在读取时被关闭的异常.记录显示终结器线程正在调用finalize()保存流的对象(进而关闭流).
代码的基本概要如下:
MIMEWriter writer = new MIMEWriter( out );
in = new InflaterInputStream( databaseBlobInputStream );
MIMEBodyPart attachmentPart = new MIMEBodyPart( in );
writer.writePart( attachmentPart );
Run Code Online (Sandbox Code Playgroud)
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 ) { }
}
Run Code Online (Sandbox Code Playgroud)
异常发生在调用链中MIMEWriter.writePart,如下所示:
MIMEWriter.writePart() 写入部件的标题,然后调用 part.writeBodyPartContent( this )MIMEBodyPart.writeBodyPartContent()调用我们的实用工具方法 …在这个答案中我发现,
在代码中使用Dispose/Finalize模式时,清除Finalize方法中的非托管资源和Dispose方法中的托管资源.
后来我发现这篇关于敲定和处理的好文章,并对它们有了清晰的认识.本文有以下代码(第3页),用于解释概念:
class Test : IDisposable
{
private bool isDisposed = false;
~Test()
{
Dispose(false);
}
protected void Dispose(bool disposing)
{
if (disposing)
{
// Code to dispose the managed resources of the class
}
// Code to dispose the un-managed resources of the class
isDisposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
Run Code Online (Sandbox Code Playgroud)
但在此之下,出现了相同的注释(我在本问题开头所包含的注释).
Dispose/Finalize模式Microsoft建议您在使用非托管资源时实现Dispose和Finalize.然后,正确的顺序是开发人员调用Dispose.即使开发人员忽略了显式调用Dispose方法,也会在对象被垃圾回收时运行Finalize实现并仍然释放资源.Francesco Balena在他的博客中写道:只有当你的类型调用分配非托管资源(包括非托管内存)的非托管代码并返回最终必须用来释放资源的句柄时,才应使用Dispose/Finalize模式.只需处理和完成必须通过在处理或完成自己的成员之后调用父母的相应方法来链接到他们的父对象". 简单地说,在代码中使用Dispose/Finalize模式时,清除Finalize方法中的非托管资源和Dispose方法中的托管资源.
现在我又困惑了.在整篇文章和代码示例中,显示应释放非托管资源Dispose().但那个评论的相关性是什么?
编辑:
正如确认这一行:
简单地说,当在代码中使用Dispose/Finalize模式时,清理Finalize方法中的非托管资源和Dispose方法中的托管资源.
是错误的,我编辑了这个答案.
今天我正在Closeablekotlin中实现一个,就像我过去在java中所做的那样,我希望finalize()在客户端代码忘记关闭它的情况下实现一个最后的回退,使得关键资源无法回收.尽管这种回退不可靠,但我认为这种资源至关重要,足以增加这种后备.但是,kotlin.Any没有声明一个finalize方法,这意味着我不能简单地这样做:
class Resource: Closeable {
fun close() {}
override fun finalize() { close()}
}
Run Code Online (Sandbox Code Playgroud)
这不好,至少没有它应该的那么好.现在我恢复普通Java作为解决方法.有谁知道如何在纯Kotlin中做到这一点?
PS:我目前的解决方法:
FinalizedCloseable.java:
public abstract class FinalizedCloseable implement Closeable {
@Override protected void finalize() { close(); }
}
Run Code Online (Sandbox Code Playgroud)
科特林:
class Resource: FinalizedCloseable(), Closeable {
fun close() {}
override fun finalize() { close()}
}
Run Code Online (Sandbox Code Playgroud)
但是这种解决方法需要一个超类.如果下次我的其他人Resource已经有了一个超类,那么如果没有很多样板,这种解决方法将无法工作.
编辑:现在我知道如何实现finalize(),但IDEA kotlin插件不够聪明,不知道这是一个终结器,因此用一些警告标记它.经过一段时间的努力,我发现如何压制这些警告,我想分享它:
class C {
@Suppress("ProtectedInFinal", "Unused") protected fun finalize() {}
}
Run Code Online (Sandbox Code Playgroud) 如果我finalize()从程序代码中调用一个对象,当垃圾回收器处理这个对象时,JVM是否仍会再次运行该方法?
这将是一个近似的例子:
MyObject m = new MyObject();
m.finalize();
m = null;
System.gc()
Run Code Online (Sandbox Code Playgroud)
显式调用是否finalize()会使JVM的垃圾收集器不在finalize()对象上运行该方法m?
可能重复:
Finalize vs Dispose
嗨,
最近我在一次关于敲定和处置的采访中被问到了.何时使用它们以及垃圾收集器如何与它们相关.请分享链接,以更多地了解该主题.
请分享......
提前致谢.
我正在慢慢地完成Bruce Eckel的Thinking in Java第4版,以下问题让我感到难过:
使用finalize()方法创建一个打印消息的类.在main()中,创建一个类的对象.修改上一个练习,以便始终调用finalize().
这是我编码的:
public class Horse {
boolean inStable;
Horse(boolean in){
inStable = in;
}
public void finalize(){
if (!inStable) System.out.print("Error: A horse is out of its stable!");
}
}
public class MainWindow {
public static void main(String[] args) {
Horse h = new Horse(false);
h = new Horse(true);
System.gc();
}
}
Run Code Online (Sandbox Code Playgroud)
它创建一个Horse布尔值inStable设置为的新对象false.现在,在该finalize()方法中,它检查是否inStable是false.如果是,则打印一条消息.
不幸的是,没有打印消息.由于条件评估为true,我的猜测是finalize()首先没有被调用.我已经多次运行该程序,并且已经看到错误消息只打印了几次.我的印象是,当System.gc()调用时,垃圾收集器将收集任何未引用的对象.
谷歌搜索一个正确的答案给了我这个链接,它提供了更详细,更复杂的代码.它采用了我以前从未见过的方法,如 …
我在java的system.gc()和finalize()方法之间混淆.我们不能强制将垃圾对象收集到JVM.我们允许在我们的java代码中编写这两种方法然后如果两者都用于垃圾收集,那么为java提供两种垃圾收集方法有什么意义呢?
请告诉我两种方法的确切工作方式以及内部工作原理如何?
如果Java finalize方法中存在无限循环或死锁,那么Finalizer线程会做什么.
在Java中,覆盖该finalize方法会得到一个糟糕的说唱,虽然我不明白为什么.类似于FileInputStream使用它来确保close在Java 8和Java 10中调用.然而,Java 9引入java.lang.ref.Cleaner了使用PhantomReference机制而不是GC终结.起初,我认为这只是向第三方类添加finalization的一种方式.但是,其javadoc中给出的示例显示了一个可以使用终结器轻松重写的用例.
我应该finalize用Cleaner 重写我的所有方法吗?(当然,我没有很多.只是一些使用OS资源的类,特别是对于CUDA互操作.)
据我所知,Cleaner(通过PhantomReference)避免了一些危险finalizer.特别是,您无法访问已清理的对象,因此您无法复活它或其任何字段.
然而,这是我能看到的唯一优势.清洁工也是非平凡的.事实上,它和终结都使用了ReferenceQueue!(难道你不喜欢阅读JDK是多么容易吗?)它比完成更快吗?它是否避免等待两个GC?如果许多对象排队等待清理,它会避免堆耗尽吗?(所有这些的答案在我看来都不是.)
最后,实际上没有任何保证阻止您在清理操作中引用目标对象.小心阅读长API注意!如果你最终引用了这个对象,那么整个机制将会默默地中断,而不像终结总是试图跛行.最后,虽然终结线程由JVM管理,但创建和保存Cleaner线程是您自己的责任.
在Java9中,终结器已经被弃用,并且已经引入了新的清洁器概念.它的具体原因是什么?是否有任何特定的场景或理由,清洁剂应优先于终结者(假设不推荐使用它们).