Login | Register   
RSS Feed
Download our iPhone app
Browse DevX
Sign up for e-mail newsletters from DevX


How to Handle Java Finalization's Memory-Retention Issues : Page 3

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.




Full Text Search: The Key to Better Natural Language Queries for NoSQL in Node.js

An Alternative to Finalization

The example in the previous section still has one source of non-determinism: the JVM does not guarantee the order in which it will call the finalizers of the objects in the finalization queue. And finalizers from all classes (application, libraries, etc.) are treated equally. So, an object that is holding on to a lot of memory or a scarce native resource can get stuck in the finalization queue behind objects whose finalizers are making slow progress (not necessarily maliciously; maybe due to sloppy programming).

To avoid this type of non-determinism, you can use weak references, instead of finalization, as the postmortem hook. This way, you have total control over how to prioritize the reclamation of native resources, instead of relying on the JVM to do so. The following example illustrates this technique:

final class NativeImage3 extends WeakReference<Image3> { // pointer to the native image data private int nativeImg; // it disposes of the native image; // successive calls to it will be ignored private native void disposeNative(); void dispose() { disposeNative(); refList.remove(this); } static private ReferenceQueue<Image3> refQueue; static private List<NativeImage3> refList; static ReferenceQueue<Image3> 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 is identical to Image2. NativeImage3 is similar to NativeImage2, but its postmortem cleanup relies on weak references instead of finalization. NativeImage3 extends WeakReference, whose referent is the associated Image3 instance. Remember that when the referent of a reference object (in this case a WeakReference) becomes unreachable, the reference object is added to the reference queue associated with it. Embedding nativeImg into the reference object itself ensures that the JVM will enqueue exactly what is needed and nothing more (see Figure 7). Again, NativeImage3 should not be an inner class of Image3, for the reasons previously outlined.

Figure 7. Embedding nativeImg into the Reference Object Itself

You can determine whether the referent of a reference object has been reclaimed by the garbage collector in two ways: explicitly, by calling the get() method on the reference object, or implicitly, by noticing that the reference object has been enqueued on the associated reference queue. This example uses only the latter.

Notice that reference objects are discovered only by the garbage collector and added to their associated references queues only if they are reachable themselves. Otherwise, they are simply reclaimed like any other unreachable object. This is why you add all NativeImage3 instances to the static list (actually, any data structure will suffice): to ensure that they remain reachable and processed when their referents become unreachable. Naturally, you also have to make sure that you remove them from the list when you dispose of them (this is done in the dispose() method).

When the dispose() method is explicitly called on an Image3 instance, no postmortem cleanup will subsequently take place on that instance; correctly so, too, as none is necessary. The dispose() method removes the NativeImage3 instance from the static list so that it is not reachable when its corresponding Image3 instance becomes unreachable. And, as previously stated, unreachable reference objects are not added to their corresponding reference queues. In contrast, in all the previous examples that use finalization, the finalizable objects will always be considered for finalization when they become unreachable, whether you have explicitly disposed of their associated native resources or not.

The JVM will ensure that when an Image3 instance is found to be unreachable by the garbage collector it adds its corresponding NativeImage3 instance to its associated reference queue. It is then up to you to dequeue it and dispose of its native resource. This can be done with a loop like the following, executed, say, on a "clean up" thread:

ReferenceQueue<Image3> refQueue = NativeImage3.referenceQueue(); while (true) { NativeImage3 nativeImg = (NativeImage3) refQueue.remove(); nativeImg.dispose(); }

This is a simplistic example. Sophisticated developers can also ensure that different reference objects are associated with different reference queues, according to how they need to prioritize their disposal. And a single "clean up" thread can poll all the available reference queues and dequeue objects according to the required priorities. Additionally, you can choose to spread out the reclamation of resources so that it is less disruptive to the application.

Even though cleaning up resources in this way is clearly a more involved process than using finalization, it is also more powerful and more flexible, and it minimizes a lot of the non-determinism associated with the use of finalization. It is also very similar to the way finalization is actually implemented within the JVM. I recommend this approach for projects that explicitly use a lot of native resources and need more control when cleaning them up. Using finalization with care would suffice for most other projects.

[Note: This article covered only two types of issues that arise when using finalization, namely memory- and resource-retention issues. The use of finalization and the Reference classes can also cause very subtle synchronization problems. Read Hans-J. Boehm's Finalization, Threads, and the Java Technology-Based Memory Model for a good overview of these.]

Comment and Contribute






(Maximum characters: 1200). You have 1200 characters left.



Thanks for your registration, follow us on our social networks to keep up-to-date