Finalization,final规则

  Finalization,final规则

  这篇文章不好找,总是出现网络访问错误,于是粘上来吧,附带网址:

  http://爪哇。星期日com/developer/technical articles/javase/finalization/http://www。德弗克斯。com/Java/Article/30192/0/page/3

  终结是爪哇编程语言的一个特性,它允许您对垃圾收集器发现无法到达的对象执行事后清理。它通常用于回收与对象关联的本机资源。下面是一个简单的终结示例:

  公共类Image1 { //指向原生图像数据的指针私有int nativeImg私人积分刷卡机私有维度dim//它处置本机映像;//对它的连续调用将被忽略私有native void dispose native();public void dispose(){ dispose native();}受保护的void finalize(){ dispose();}静态私有图像随机化}在图1实例变得不可访问后的某个时间,Java虚拟机(JVM)*将调用其最终确定()方法,以确保保存图像数据的本机资源(在本例中由整数自然指向)已被回收。

  但是,请注意,尽管最终确定()方法被虚拟机(Java Virtual Machine的缩写)进行了特殊处理,但它是一个包含任意代码的任意方法。特别是,它可以访问宾语位置和暗淡的中的任何字段。令人惊讶的是,它还可以使对象再次可达,比如说,使其从静态字段可达,例如,randomImg=this .不推荐后一种编程实践,但不幸的是爪哇编程语言允许这样做。

  下面的步骤和图一描述了一个可终结对象目标文件的生命周期——也就是说,这个对象的类有一个重要的终结器。

  图一。可终结对象目标文件的生存期。

  当目标文件被分配时,JVM在内部记录目标文件是可终结的。这通常会减慢现代虚拟机(Java Virtual Machine的缩写)的快速分配路径。

  当垃圾收集器确定目标文件不可达时,它会注意到目标文件是可终结的——因为它是在分配时被记录的——并将它添加到虚拟机(Java Virtual Machine的缩写)的终结队列中。它还确保保留所有可从目标文件访问的对象,即使它们是不可访问的,因为它们可能会被终结器访问。图2针对对象图1的一个实例说明了这一点。

  图二。垃圾收集器确定目标文件不可达。

  稍后,JVM的终结器线程将让目标文件出队,调用它的最终确定()方法,并记录目标文件的终结器已被调用。至此,对象算是定案了。

  当垃圾收集器重新发现目标文件不可达时,它将回收它的空间以及从它可达的所有东西,前提是后者不可达。请注意,垃圾收集器至少需要两个周期来回收obj,并需要在此过程中保留所有其他可从目标文件访问的对象。如果程序员不小心,这可能会造成暂时的、微妙的和不可预测的资源保留问题。此外,JVM不保证它会调用所有已分配的可终结对象的终结器。它可能会在垃圾收集器发现其中一些不可到达之前退出。

  当子类化终结会延迟资源的回收时,避免内存保留问题,即使您没有显式地使用它。考虑下面的例子:

  公共类RGB图像1扩展图1 {私有字节RGB数据[];RGB图像1类扩展了图1并引入了字段RGB数据——可能还有一些示例中没有显示的方法。即使您没有在rgbi图像1上显式定义终结器,该类也将自然地从图1继承最终确定()方法,并且所有rgbi图像1实例也被认为是可终结的。当RGB图像1实例变得不可访问时,潜在的非常大的rgbData数组的回收将被延迟,直到实例完成,如图3所示。这种内存保留问题可能很难发现,因为终结器可能"隐藏"在深的类层次结构中。

  图3。rgbData数组的回收将被延迟,直到实例完成。避免此问题的一种方法是重新安排代码,使其使用组合而不是继承,如下所示:

  公共类rgbi图像2 {私有图像1 img私有字节rgbData[];public void dispose(){ img。dispose();} }另见约书亚布洛赫的书《有效的爪哇编程语言指南》,第四章,第14项:重组合轻继承。

  与RGB图像1相比,RGB图像2包含图1的实例,而不是扩展图1 .当RGB图像2的一个实例变得不可访问时,垃圾收集器将立即回收它和rgbData数组——假设后者从其他地方不可访问——并将只对图1实例进行排队以进行终结,如图四所示。因为RGB图像2类没有图1的子类,所以它不会从图1继承任何方法。因此,您可能需要将委托者方法添加到RGB图像1中,以访问图1所需的方法dispose()方法就是这样一个例子。

  图4。乔治勋章将只对图1实例进行排队以进行终结。然而,你不能总是以刚才描述的方式重新安排你的代码。有时,作为该类的用户,您必须做更多的工作来确保其实例在完成时不会占用过多的空间。以下代码说明了如何做到这一点:

  公共类RGB图像3扩展图1 {私有字节RGB数据[];public void dispose(){ rgbData=null;超级棒。dispose();} }图像3与RGB图像1相同,但增加了处置()方法,该方法使rgbData字段为空。在使用RGB图像3实例后,需要显式调用dispose(),以确保rgbData数组被迅速回收,如图5所示。显式地将字段置空很少是好的做法,但是这是极少数有理由这样做的情况之一。

  图5。使用RGB图像3实例后调用处置().

  本文描述了在使用使用终结器的第三方类时如何避免内存保留问题。现在让我们看看如何编写需要事后清理的类,以便它们的用户不会遇到前面概述的问题。最好的方法是将这样的类一分为二——一个保存需要事后清理的数据,另一个保存所有其他的东西——并且只在前者上定义一个终结器。以下代码说明了这种技术:

  最后的类NativeImage2 { //指向原生图像数据私有int nativeImg的指针;//它处置本机映像;//对它的连续调用将被忽略私有native void dispose native();void dispose(){ dispose native();}受保护的void finalize(){ dispose();} } public class Image2 { private native Image2 native img;私人积分刷卡机私有维度dim public void dispose(){ native img。dispose();} }图像2实例类似于图1,但原生图像字段包含在单独的类原生图像2中。从图像类对自然的所有访问都必须经过一级间接访问。但是,当图2实例变得不可访问时,只有原生图像2实例将排队等待完成。从图2实例可以到达的任何东西都将被立即回收,如图6所示。类原生图像2被声明为最终,这样用户就不能对它进行子类化,从而重新引入本文前面描述的内存保留问题。

  图6。当图2实例变得不可访问时,只有原生图像2实例将排队。微妙的一点是,本机图像2不应该是图2的内部类。内部类的实例有一个对创建它们的外部类实例的隐式引用。因此,如果原生图像2是图2的内部类,并且一个原生图像2实例正在排队等待终结,它也将保留相应的图2实例,这正是您试图避免的。但是,假设只能从图2类访问原生图像2类。这就是它没有公共方法的原因。它的处置()方法以及类本身都是包私有的。

  终结的替代方法前面的例子仍然有一个不确定性的来源:JVM不保证它调用终结队列中对象的终结器的顺序。所有类(应用程序、库等等)的终结器都被同等对待。因此,占用大量内存或稀缺本机资源的对象可能会卡在终结器进展缓慢的对象后面的终结队列中——这不一定是恶意的,但可能是由于草率的编程。

  为了避免这种类型的不确定性,可以使用弱引用代替终结作为事后通知机制。这样,您可以完全控制如何确定本机资源回收的优先级,而不是依赖虚拟机(Java Virtual Machine的缩写)来完成。以下示例说明了这种技术:

  最后的类原生图像3扩展WeakReference Image3 { //指向原生图像数据私有int nativeImg的指针;//它处置本机映像;//对它的连续调用将被忽略私有native void dispose native();void dispose(){ ref list。去掉(这个);处置native();}静态私有参考队列图像3参考队列静态个人分发名单NativeImage3引用列表静态引用队列Image3引用队列(){返回引用队列;}原生Image3(Image3 img){ super(img,ref queue);参考列表。补充(这个);} }公有类Image3 {私有原生Image3原生img私人积分刷卡机私有维度dim public void dispose(){ native img。dispose();} }图像3与图像2相同100 . native image 3类似于NativeImage2,但它的事后清理依赖于弱引用,而不是终结100 . native image 3扩展了WeakReference,其指示物是关联的图3实例。请记住,当引用对象的指示物在本例中是弱引用——变得不可访问时,该引用对象将被添加到与之关联的引用队列中。将自然嵌入到引用对象本身可以确保虚拟机(Java Virtual Machine的缩写)只将需要的东西排队,而不是更多。参见图7。同样,出于前面概述的原因,本机图像3不应该是图3的内部类。

  图7。将自然嵌入引用对象本身。您可以通过两种方式确定垃圾收集器是否已经回收了引用对象的参考对象:显式地,通过调用引用对象上的获取()方法,或者隐式地,通过注意引用对象已经在关联的引用队列中排队。这个例子只使用了后者。

  请注意,引用对象是由垃圾收集器发现的,并且只有在它们自己可以到达的情况下,才会被添加到它们关联的引用队列中。否则,它们就像任何其他不可到达的对象一样被简单地回收。这就是为什么要将所有原生图像3实例添加到静态列表中——实际上,任何数据结构都足够了——以确保当它们的引用变得不可访问时,它们仍可访问并被处理。当然,您还必须确保在处理它们时将它们从列表中删除。这是在处置()方法中完成的。

  当在图3实例上显式调用处置()方法时,随后不会在该实例上进行事后清理,因为不需要进行任何清理dispose()方法从静态列表中删除原生图像3实例,以便当其对应的图3实例变得不可访问时,它也不可访问。并且,如前所述,不可到达的引用对象不会被添加到它们相应的引用队列中。

  相比之下,在前面所有使用终结的示例中,当可终结对象变得不可访问时,将始终考虑对其进行终结,无论您是否已显式释放了它们的关联本机资源。

  虚拟机(Java Virtual Machine的缩写)将确保,当垃圾收集器发现图3实例不可达时,它会将其对应的原生图像3实例添加到其关联的引用队列中。然后,您必须将其出队并释放其本机资源。你可以用下面的方法做到这一点,比如说,在一个"清理"线程上执行:

  静态void drainRefQueueLoop(){引用队列映像3 ref queue=本机映像3。引用队列();while(true){原生映像3原生img=(原生映像3)ref队列。移除();原生img。dispose();} }然而,在某些情况下,在应用程序中引入一个新线程可能并不容易或不理想。在这种情况下,一种替代方法是在每个原生图像3实例分配之前清空引用队列。您可以通过调用drainRefQueueBounded()方法来实现这一点,该方法来自原生图像3构造函数,这样您就可以在需要分配新映像之前释放一些可用的本机映像:

  static final private int MAX _ ITERATIONS=2;静态void drainRefQueueBounded(){引用队列映像3 ref queue=本机映像3。引用队列();(同Internationalorganizations)国际组织迭代次数=0;while(ITERATIONS MAX _ ITERATIONS){原生映像3原生img=(原生映像3)ref队列。poll();if(native img==null){ break;}原生img。dispose();迭代次数;drainRefQueueLoop()和drainRefQueueBounded()之间的主要区别在于,前者是一个无限的操作移除()方法会一直阻塞,直到队列中有新的条目可用为止——而后者只做有限的工作。如果队列中没有条目,投票()方法将返回空,并且该方法将只循环最大迭代次数次,因此如果引用队列很长,它不会花费任意长的时间。

  前面的例子非常简单。经验丰富的开发人员还可以确保不同的引用对象与不同的引用队列相关联,这取决于他们需要如何确定其处置的优先级drainRefQueueLoop()或drainRefQueueBounded()方法可以轮询所有可用的引用队列,并根据所需的优先级将对象出队。

  虽然用这种方式清理资源显然比使用终结更复杂,但它也更强大、更灵活,并且最大限度地减少了与使用终结相关的不确定性。这也非常类似于最终化在虚拟机(Java Virtual Machine的缩写)中的实际实现方式。对于明确使用大量本机资源并且在清理过程中需要更多控制的项目,建议使用这种方法。对于大多数其他项目来说,小心使用终结就足够了。

  本文简要描述了在虚拟机(Java Virtual Machine的缩写)中如何实现终结。然后给出了可终结对象如何不必要地保留内存的例子,并概述了这些问题的解决方案。最后,它描述了一种使用弱引用而不是终结的方法,这种方法允许您以更加灵活和可预测的方式执行事后清理。

  然而,完全依赖垃圾收集器来识别不可到达的对象,以便回收它们相关的本机(和潜在的稀缺)资源有一个严重的缺陷:内存通常是充足的,用充足的内存来保护潜在的稀缺资源不是一个好策略。因此,当您使用一个已知具有相关联的本机资源的对象时——例如,一个图像使用者界面组件、文件或套接字——当您使用完它时,一定要调用它的处置()或等效方法。这将确保立即回收自然资源,并降低资源枯竭的可能性。因此,您将使用本文中讨论的方法进行事后清理,只是作为最后的手段,而不是作为主要的清理机制。

  您也应该只在绝对必要时使用终结。终结是一个不确定的——有时是不可预测的——过程。对它的依赖越少,它对虚拟机(Java Virtual Machine的缩写)和应用程序的影响就越小。另请参见约书亚布洛赫的书《有效的爪哇编程语言指南》,第2章,第6项:避免终结器。

  注意:本文只讨论了使用终结时出现的两种问题:内存和资源保留问题。终结和引用类的使用也会导致非常微妙的同步问题。请参阅汉斯博恩的2005 JavaOne大会幻灯片《终结、线程和基于爪哇技术的内存模型》,对这些问题有一个很好的概述。

  *本网站上使用的术语“爪哇虚拟机"或" JVM "是指用于爪哇平台的虚拟机。

  更多信息请见约书亚布洛赫。有效的爪哇编程语言指南。艾迪森-韦斯利,2001年。

  汉斯j博姆。终结、线程和基于爪哇技术的内存模型2005 JavaOne大会大会技术会议3281。

  作者感谢凯斯勒和布莱恩戈茨对本文提出的建设性意见。

  2005年12月27日,DevX.com上发表了一篇略有不同的文章。

  关于作者托尼普林泰齐斯是太阳微系统公司Java热点虚拟机开发团队的成员。他将大部分时间花在动态内存管理上,专注于垃圾收集器的可伸缩性、响应性、并行性和可视化。

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: