四、
一种代替最终化的选择
在前面一节中的示例还存在一种不确定性可能:JVM并不能保证它在最终化队列中调用对象的终结器的顺序。而来自于所有类(应用程序,库,等等)的终结器都是被同等对待的。因此,一个占有大量内存或一种稀有的本地资源的对象可能受阻于终结化队列-它们排在那些终结器进度缓慢的对象之后(不一定是恶意;也许由于懒惰的编程所致)。
看完上面达内培训老师分享的的第一段内容之后大家是不是已经知道今天我们要为大家分享什么内容了那?
为了避免这种类型的不确定性,你可以使用弱参考来代替最终化,例如使用死后钩子(postmortem
hook)。如果用这种方式,你可以完全控制怎样优先化本地资源的回收问题,而代替依赖于JVM完成这件事情。下面的示例展示了这一技术:
final
class NativeImage3 extends WeakReference {
private
int nativeImg;//指向本地图像数据
//它释放本地图像;随后对它的调用将被忽略
private
native void disposeNative();
void
dispose() {
disposeNative();
refList.remove(this);
}
static
private ReferenceQueue refQueue;
static
private List refList;
static
ReferenceQueue referenceQueue() {return refQueue;}
NativeImage3(Image3
img) {
super(img,
refQueue);
refList.add(this);
}
}
public
class Image3 {
private
NativeImage3 nativeImg;
private
Point pos;
private
Dimension dim;
public
void dispose() { nativeImg.dispose(); }
}
Image3与Image2相同。NativeImage3相似于NativeImage2,但是它的最后清理依赖于弱参考而不是最终化。NativeImage3扩展WeakReference,其参考是与之相关联的Image3实例。请记住,当一个参考对象的参考(此时是WeakReference)成为不可达的时,该参考对象就被添加到与之相关联的参考队列上。把nativeImg嵌入到参考对象本身就保证JVM会正确地把所需要的加入到队列中(见图6)。再强调一下,NativeImage3不应该成为Image3的一个子类,这是基于前面所述原因。
图6.把nativeImg嵌入到Reference对象本身
你可以决定是否一参考对象的参考物已经被垃圾收集器以两种方式回收:显式地,在参考对象上调用get()方法;隐式地,通过观察参考对象已经在相关联的参考队列中排队来实现。本示例中只使用了后者。
注意,参考对象仅能被垃圾收集器所发现并且被添加到它们的相关联的参考队列-只有它们本身是可达的时候。否则,它们就象任何其它不可达的对象一样被简单地回收。这就是为什么你把所有的NativeImage3实例添加到该静态链表(实际上,任何数据结构都会满足):为了确保它们保持为可达的并且当它们的参考物成为不可达的时被处理。当然,你还必须确保当你释放它们时(这是通过dispose()方法来实现的)你从该列表中删除了它们。
当在一个Image3实例上显式地调用dispose()方法时,在该实例上不会发生随后的最后清理;正确情况下也是这样,因为这里不需要任何东西。这个dispose()方法从静态列表中删除NativeImage3实例,这样当它的相应的Image3实例成为不可达的时它就是不可达的。并且,如前所述,不可达的参考对象并不被添加到它们相应的参考队列。相反,在所有前面的使用了最终化的示例中,可最终化的对象将总是被作最终化考虑-当它们成为不可达的时候,无论你是否已显式地释放它们相关联的本地资源。
JVM将保证,当通过垃圾收集器发现一个Image3实例是不可达的时候,它会把它的相应的NativeImage3实例添加到它的相关联的参考队列上去。然后,由你负责把它从队列中删除并释放它的本地资源。这可以通过在一个"清理"线程中,用一个如下的循环来实现:
ReferenceQueue
refQueue =NativeImage3.referenceQueue();
while
(true) {
NativeImage3
nativeImg =(NativeImage3) refQueue.remove();
nativeImg.dispose();
}
这是一个过于简单的实例。高级开发者能另外根据它们如何需要优先化清理来确保不同参考对象关联于不同的参考队列。并且一个单个的"清理"线程可以查询所有可用的参考队列和根据要求的优先级来从队列中删除对象。另外,你可以选择展开(spread
out)回收资源,这样它就会给应用程序带来更少的危险性。
尽管用这种方式清理资源与使用最终化相比,明显是更复杂些,但是这也是一种更为有力量和更为灵活的方式,而且可以最小化大量的与使用最终化相关的不确定性。另外,这种方式还十分相似于最终化实际在JVM内实现的方式。对于那些显式地使用很多本地资源并且需要更多控制的工程,我推荐对它们进行清理时使用这一方法。而只要小心地使用最终化对于大多数另外的工程来说也就足够了。
注意:本文仅讨论了两种类型的在使用最终化时产生的问题,也就是内存和资源保留问题。最终化和参考类的使用也能带来很微妙的同步问题。要想详细了解这一点,可以参考Read
Hans-J.Boehm的《最终化,线程和基于Java技术的内存模型》一文。
五、仅在必要时才使用最终化
本文简短描述了最终化是怎样在JVM中实现的。然后给出了有关内存是怎样不必被可最终化的对象所保留的示例并且概括了这种问题的解决方案。最后,我描述了一个方法-它使用弱参考来代替-这允许你用一种更为灵活和可预测的方式来执行最后清理。