How to Handle Java Finalization's Memory-Retention Issues

原文地址:http://www.devx.com/Java/Article/30192

Finalization allows you to perform postmortem cleanup on Java objects, but it can delay the reclamation of resources, even if you do not use it explicitly. Learn how to avoid such memory-retention problems.

Finalization is a feature of the Java programming language that allows you to perform postmortem cleanup on objects that the garbage collector has found to be unreachable. It is typically used to reclaim native resources associated with an object. The following is a simple finalization example:

public class Image1 {
	// pointer to the native image data
	private int nativeImg;
	private Point pos;
	private Dimension dim;

	// it disposes of the native image;
	// successive calls to it will be ignored
	private native void disposeNative();
	public void dispose() { disposeNative(); }
	protected void finalize() { dispose(); }

	static private Image1 randomImg;
}

Some time after an Image1 instance has become unreachable, the Java virtual machine (JVM) will call its finalize() method to ensure that the native resource that holds the image data (pointed to by the integer nativeImg in the example) has been reclaimed. Notice, however, that the finalize() method, despite its special treatment by the JVM, is an arbitrary method that contains arbitrary code. In particular, it can access any field in the object (pos and dim in the example). Surprisingly, it can also make the object reachable again by, say, making it reachable from a static field (e.g., randomImg = this;). I really don't recommend the latter programming practice, but unfortunately the Java programming language allows it.

 

The following steps describe the lifetime of a finalizable object obj—that is, an object whose class has a non-trivial finalizer (see Figure 1):

How to Handle Java Finalization's Memory-Retention Issues

  1. When obj is allocated, the JVM internally records that obj is finalizable (this typically slows down the otherwise fast allocation path that modern JVMs have).
  2. When the garbage collector determines that obj is unreachable, it notices that obj is finalizable (as it had been recorded upon allocation) and adds it to the JVM's finalization queue. It also ensures that all objects reachable from obj are retained, even if they are otherwise unreachable, as they might be accessed by the finalizer. Figure 2 illustrates this for an instance of Image1.
  3. At some point later, the JVM's finalizer thread will dequeue obj, call its finalize() method, and record that obj's finalizer has been called. At this point, obj is considered to be finalized.
  4. When the garbage collector rediscovers that obj is unreachable, it will reclaim its space along with everything reachable from it (provided that the latter is otherwise unreachable).How to Handle Java Finalization's Memory-Retention Issues

Notice that the garbage collector needs a minimum of two cycles (maybe more) to reclaim obj and needs to retain all other objects reachable from obj during this process. If a programmer is not careful, this can create temporary, subtle, and unpredictable resource-retention issues. Additionally, the JVM does not guarantee that it will call the finalizers of all the finalizable objects that have been allocated; it might exit before the garbage collector discovers some of them to be unreachable.

总结:一些应用程序通过使用finalization和弱引用、软引用或虚引用与垃圾收集进行交互。这些特性可以在Java编程语言级别创建性能工件。这方面的一个例子是依靠finalization来关闭文件描述符,这使得外部资源文件的关闭依赖于垃圾收集的及时性(垃圾收集很可能很久之后才会执行)。依赖垃圾收集来管理内存以外的资源几乎总是一个坏主意。请注意,垃圾收集器至少需要两个循环(可能更多)来回收finalizable对象,并且需要在此过程中保留obj可访问的所有其他对象并且这可能会造成暂时的、微妙的和不可预测的资源保留问题。