Java Top.

开始使用Spring 5和Spring Boot 2,通过学习春天课程:

>>查看课程

1.概述

在本教程中,我们将专注于Java语言的核心方面 -最终确定由根提供的方法目的班级。

简单地说,这在特定对象的垃圾收集之前调用。

2.使用终结器

finalize ()方法称为终结器。

当JVM发现这个特定实例应该被垃圾收集时,就会调用终结器。这样的终结器可以执行任何操作,包括使对象恢复生命。

然而,终结器的主要目的是在对象从内存中删除之前释放它们使用的资源。收尾器可以作为清理作业的主要机制,也可以在其他方法失效时作为安全网。

要了解最终结果的工作原理,让我们来看看课堂声明:

公共类最终可达{私人BufferedReader Reader;public linalizable(){inputstream input = this.getClass().getClassLoader().getResourceastream(“file.txt”);this.reader =新的bufferedreader(新的InputStreamReader(输入));}公共字符串readfirstline()抛出IoException {String FirstLine = Reader.readline();返回第一行;} //其他类别成员}

班上最终可爱的有一个领域读者,它引用一个可关闭的资源。当从这个类创建对象时,它会构造一个新的BufferedReader.从类路径中的文件进行实例读取。

这样的实例是使用的readfirstline.方法提取给定文件中的第一行。请注意,读者在给定代码中未关闭。

我们可以使用终结器进行:

@override public void finalize(){try {reader.close();system.out.println(“终结器中的”Clouded BufferedReader“);catch(ioException e){// ...}}

很容易看出终结器就像任何正常实例方法一样声明。

在现实中,垃圾收集器调用终结器的时间取决于JVM的实现和系统的条件,这是我们无法控制的。

为了让垃圾收集在现场发生,我们将利用system . gc方法。在真实世界的系统中,出于多种原因,我们永远不会明确调用这一点:

  1. 这是昂贵的
  2. 它不会立即触发垃圾收集 - 它只是JVM启动GC的提示
  3. JVM更清楚什么时候需要调用GC

如果我们需要强制GC,我们可以使用jconsole为了那个原因。

以下是测试用例,演示了终结器的操作:

@test public void whengc_thenfinalizereexecuted()抛出IoException {String FirstLine = new finalizable()。readfirstline();assertequals(“金宝搏188体育baeldung.com”,第一列);system.gc();}

在第一个表述中,a最终可爱的对象是创建的,然后是它的readfirstline.方法被调用。此对象没有赋值给任何变量,因此当system . gc方法被调用。

测试中的断言验证了输入文件的内容,用于证明我们的自定义类按预期工作。

当我们运行所提供的测试时,将在控制台上打印一条关于在终结器中关闭缓冲读取器的消息。金宝搏官网188be这意味着最终确定方法,它已清除了资源。

到目前为止,Finalizers看起来像预破坏操作的好方法。但是,这不是真的。

在下一节中,我们将看到为什么应该避免使用它们。

3.避免终结器

尽管他们带来了好处,但总结者有很多缺点。

3.1。终结者的缺点

让我们来看看当使用终结器执行关键操作时,我们将面临的几个问题。

第一个值得注意的问题是不够及时。我们无法知道终结器何时运行,因为垃圾收集可能随时发生。

就其本身而言,这不是问题,因为终结器迟早仍会执行。然而,系统资源并不是无限的。因此,我们可能在清理发生之前耗尽资源,这可能导致系统崩溃。

终结器对程序的可移植性也有影响。由于垃圾收集算法依赖于JVM实现,所以一个程序可能在一个系统上运行得很好,但在另一个系统上的行为却不同。

绩效成本是终结器附带的另一个重要问题。具体来说,在构造和销毁包含非空终结器的对象时,JVM必须执行更多操作

我们谈论的最后一个问题是在最终确定期间缺乏异常处理。金宝搏官网188be如果终结器抛出异常,则最终化进程停止,在没有任何通知的情况下将对象以损坏的状态留出。

3.2。演示终结器的影响

现在是时候把理论放在一边,看看终结者在实践中的影响。

让我们使用非空终结器定义一个新类:

public class CrashedFinalizable {public static void main(String[] args) throws ReflectiveOperationException {for (int i = 0;;i++) {new CrashedFinalizable();//其他代码}}@Override protected void finalize() {System.out.print("");}}

注意到finalize ()方法——它只是向控制台输出一个空字符串。如果这个方法是完全空的,那么JVM会将对象视为没有终结器。因此,我们需要提供finalize ()使用一个实现,它在本例中几乎什么都不做。

在 - 的里面主要的方法,一个新的崩溃可靠实例是在每次迭代中创建的为了循环。此实例没有分配给任何变量,因此适合进行垃圾收集。

让我们在标有的行中添加一些陈述//其他代码查看运行时内存中存在多少对象:

if((i%1_000_000)== 0){class <?> finalizerClass = class.forname(“java.lang.ref.finalizer”);字段queuestaticfield = finalizerclass.getdeclaredfield(“队列”);queuestaticfield.setCrocessible(真实);roaditueue  Referenceue =(参考)queuestaticfield.get(null);Field Queuel LengthField = ReguentQueue.Class.getDeclaredField(“QueuelThength”);queuel lengthfield.setCrocessible(真实);long queuelhength =(long)queuelthencefield.get(参考);system.out.format(“队列%n”中有%d引用,queuelthength);}
         

给定的语句访问内部JVM类中的某些字段,并在每百万迭代后打印出对象引用的数量。

让我们通过执行来启动程序主要的方法。我们可能希望它无限期地营运,但情况并非如此。几分钟后,我们应该看到系统崩溃与类似的错误:

…有21914844引用队列中有22858923引用队列中有22858923引用队列中有24621725引用队列中有25410983引用队列中有25410983引用队列中有26975913引用队列异常的线程“主要”java.lang.OutOfMemoryError: GC开销限制超过java.lang.ref.Finalizer.register (Finalizer.java: 91)在java . lang . object。< init > (Object.java: 37) com.baeldung.finalize.金宝搏188体育CrashedFinalizable。< init > (CrashedFinalizable.java: 6) com.baeldung.finalize.CrashedFinalizable.main (CrashedFinalizable.java: 9)过程完成退出代码1

看起来垃圾收集器没有做好工作 - 对象的数量不断增加,直到系统崩溃。

如果我们删除了终结器,则引用的数量通常为0,程序将继续永久运行。

3.3。解释

要理解垃圾收集器为什么没有按照应该的方式丢弃对象,我们需要看看JVM内部是如何工作的。

创建对象时,也称为具有终结器的指示,JVM创建了伴随类型的参考对象java.lang.ref.finalizer.在referent准备好进行垃圾收集之后,JVM将该引用对象标记为准备好进行处理,并将其放入引用队列。

我们可以通过静态字段访问此队列队列在里面java.lang.ref.finalizer.班级。

同时,一个特殊的守护程序线程终结器继续运行并查找参考队列中的对象。当它找到一个时,它从队列中删除引用对象,并调用referent上的终结器。

在下一个垃圾收集周期中,将丢弃指示 - 当它不再从引用对象中引用时。

如果一个线程以高速保留产生物体,那就是我们示例中发生的事情,那么终结器线程无法跟上。最终,内存不能存储所有的对象,我们最终得到OutofMemoryError.

请注意,如本节所示,在扭曲速度下创建对象的情况不会发生在现实生活中。但是,它展示了一个重要的观点 -终结器非常昂贵。

4.没有最终的例子

让我们探索提供相同功能但不使用的解决方案finalize ()方法。请注意,以下示例不是替换Finalizer的唯一方法。

相反,它被用来演示一个重要的问题:总是有一些选项可以帮助我们避免终结器。

这是我们新课程的声明:

公共类Closeableresource实现可容易旋转{私人BufferedReader Reader;public closeableresource(){inputstream input = this.getClass().getClassLoader().getResourceastream(“file.txt”);读者=新的BufferedReader(新InputStreamReader(输入));}公共字符串readfirstline()抛出IoException {String FirstLine = Reader.readline();返回第一行;@override public void关闭(){try {reader.close();system.out.println(“关闭方法”的“Clouded BufferedReader”);catch(ioException e){// hander异常}}}

这并不难看出新的唯一差异Closeableresource.班级和我们以前的最终可爱的班级是实施可容易旋转接口,而不是终结器定义。

控件的主体部分关闭的方法Closeableresource.与类中的终结器的主体几乎相同最终可爱的

以下是一个测试方法,它读取输入文件并在完成作业后释放资源:

@test公共void whentrywresourcesexits_thenresourceclosed()抛出IoException {try(closeableresource resource = new closeableresource()){string firstline = resource.readfirstline();assertequals(“金宝搏188体育baeldung.com”,第一列);}}

在上面的测试中,一个Closeableresource.实例是在中创建的尝试try-with-resources语句的块,因此当try-with-resources块执行完成时,该资源将自动关闭。

运行给定的测试方法,我们将看到从关闭方法的方法Closeableresource.班级。

5。结论

在本教程中,我们专注于Java的核心概念 - 该最终确定方法。这在纸上看起来很有用,但在运行时可能会产生丑陋的副作用。而且,更重要的是,使用终结器总有替代的解决方案。

注意一个关键点是那个最终确定从Java 9开始弃用 - 最终将被删除。

一如既往,可以找到本教程的源代码在github上

Java底部

开始使用Spring 5和Spring Boot 2,通过学习春天课程:

>>查看课程
2注释
最老的
最新
内联反馈
查看所有评论
评论在本文上关闭!