Login | Register   
LinkedIn
Google+
Twitter
RSS Feed
Download our iPhone app
TODAY'S HEADLINES  |   ARTICLE ARCHIVE  |   FORUMS  |   TIP BANK
Browse DevX
Sign up for e-mail newsletters from DevX


advertisement
 

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

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.


advertisement

Avoid Memory-Retention Problems When Subclassing

Finalization can delay the reclamation of resources, even if you do not use it explicitly. Consider the following example:

public class RGBImage1 extends Image1 { private byte rgbData[]; }

 
Figure 3. Reclamation of rgbData Array Will Be Delayed Until the Instance Is Finalized



RGBImage1 extends Image1 and introduces field rgbData (and maybe some methods the example doesn't show). Even though you did not explicitly define a finalizer on RGBImage1, the class will naturally inherit the finalize() method from Image1, and all RGBImage1 instances will also be considered to be finalizable. When an RGBImage1 instance becomes unreachable, the reclamation of the potentially very large rgbData array will be delayed until the instance is finalized (see Figure 3). It can be a difficult problem to find because the finalizer might be "hidden" in a deep class hierarchy.

One way to avoid this problem is to re-arrange the code so that it uses the "contains," instead of the "extends," pattern, as follows:

public class RGBImage2 { private Image1 img; private byte rgbData[]; public void dispose() { img.dispose(); } }

 
Figure 4. GC Will Queue Up Only the Image1 Instance for Finalization

Compared with RGBImage1, RGBImage2 contains an instance of Image1 instead of extending Image1. When an instance of RGBImage2 becomes unreachable, the garbage collector will promptly reclaim it, along with the rgbData array (assuming the latter is not reachable from anywhere else), and will queue up only the Image1 instance for finalization (see Figure 4). Since class RGBImage2 does not subclass Image1, it will not inherit any methods from it. Therefore, you might have to add delegator methods to RGBImage1 to access the required methods of Image1 (the dispose() method is such an example).

You cannot always re-arrange your code in the manner described above, however. In that case, as a user of the class, you will have to do a little more work to ensure that its instances do not hold on to more space than necessary when they are being finalized. The following code illustrates how:

public class RGBImage3 extends Image1 { private byte rgbData[]; public void dispose() { super.dispose(); rgbData = null; } }

 
Figure 5. Call dispose() After Using an RGBImage3 Instance

RGBImage3 is identical to RGBImage1 but with the addition of the dispose() method, which nulls the rgbData field. You are required to explicitly call dispose() after using an RGBImage3 instance to ensure that the rgbData array is promptly reclaimed (see Figure 5). I recommend explicit nulling of fields on very few occasions; this is one of them.

Shield Users from Memory-Retention Problems

The previous section described how to avoid memory-retention problems when working with third-party classes that use finalizers. This section describes how to write classes that require postmortem cleanup so that their users don't encounter the problems previously outlined. The best way to do so is to split such classes into two (one to hold the data that need postmortem cleanup, the other to hold everything else) and define a finalizer only on the former. The following code illustrates this technique:

final class NativeImage2 { // 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(); } protected void finalize() { dispose(); } } public class Image2 { private NativeImage2 nativeImg; private Point pos; private Dimension dim; public void dispose() { nativeImg.dispose(); } }

 
Figure 6. When Image2 Instance Becomes Unreachable, Only the NativeImage2 Instance Will Be Queued Up

Image2 is similar to Image1, but with the nativeImg field included in a separate class, NativeImage2. All accesses to nativeImg from the image class must go through one level of indirection. However, when an Image2 instance becomes unreachable, only the NativeImage2 instance will be queued up for finalization; anything else reachable from the Image2 instance will be promptly reclaimed (see Figure 6). Class NativeImage2 is declared to be final so that users cannot subclass it and re-introduce the memory-retention problems described in the previous section.

A subtle point is that NativeImage2 should not be an inner class of Image2. Instances of inner classes have an implicit reference to the instance of the outer class that created them. Therefore, if NativeImage2 was an inner class of Image2, and a NativeImage2 instance was queued up for finalization, it would have also retained the corresponding Image2 instance, which is precisely what you are trying to avoid. Assume, however, that the NativeImage2 class will be accessible only from the Image2 class. This is the reason why it has no public methods (its dispose() method, as well as the class itself, is package-private).



Comment and Contribute

 

 

 

 

 


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

 

 

Sitemap