About Java references

2011-12-17 19:22

There is somewhat common misconception about garbage collecting, that it totally frees the programmer from memory-related concerns. Granted, it makes the task easier in great many cases, but it does so at the expense of significant loss of control over objects’ lifetime. Normally, they are kept around for at least until they are not needed anymore – and usually that’s fine for the typical definitions of “need” and “at least”. Usually – but not always.

For those less typical use cases, garbage-collected environments provide mechanisms allowing to regain some of that lost control, to the extent necessary for particular task. Java, for example, offers a variety of different types of references, enabling to change the notion of what it means for an object to be eligible for garbage collecting. Choosing the right one for a problem at hand can be crucial, especially if we are concerned with the memory footprint of our application. Since – as the proverb goes – JVM expands to fill all available memory, it’s good to know about techniques which help maintain our heap size in check.

The default is strong

So today, I will discuss the SoftReference and WeakReference classes, which can be both found in the java.lang.ref package. They provide the so-called soft and weak references, which are both considerably less powerful when it comes to prolonging the lifetime of an object.

But first, we should have a brief look at the default: strong references, which are supported directly by the Java syntax:

  1. Random random = new Random();

Because they are so widespread, they form a backbone of the graph of objects living within an application. This is how we usually visualize the heap as it’s traversed by garbage collector. The walk starts from nodes called GC roots, which are accessible from outside the heap – mostly via references residing on stacks of running threads, or in static fields. It continues through all the strong references that tie objects together, and marks nodes encountered along the way. Once the traversal ends, those that ended up unmarked are considered unreachable and therefore eligible for garbage collection.

This very brief outline of the collecting process should make it rather evident where the strong references got their name from. A single strong reference can prevent garbage collection for a subgraph of an arbitrary size. While this is often necessary (it can be, for example, conglomerate of “global” objects), it can also lead to elusive memory leaks. That’s right: even in environment with GC, it is possible to have a memory leak, and the prime reason for that is an “orphaned” strong reference.

Weakness can be a virtue, too

The upside of default, strong reference is that it is always valid within its scope. An object referenced in this ordinary way cannot just vanish between one instruction and another, swept by concurrent GC. Instead, it will persist at least until all its strong references get out of their respective scopes.

This is all trivially obvious. But if we want to use other types of Java references, this is also the first premise we should give up on. Both WeakReference and SoftReference are proper classes whose get method provide access to underlying object. And behold: this method can return null if the object is no longer available because it was reclaimed by garbage collector.

It might be hard to think about situations where such behavior could be considered totally fine and acceptable. But they are quite numerous. In fact, any kind of “incidental” relationship between objects might permit a WeakReference. Among those, there are some cases where a weak link is mandatory, for its lack pretty much warrants a memory leak. Examples include an extremely common Java pattern – a list of listeners:

  1. public class Happener {
  2.     public interface OnStuffHappenedListener {
  3.         void onStuffHappened();
  4.     };
  5.  
  6.     private List<WeakReference<OnStuffHappenedListener>> listeners
  7.         = new ArrayList<WeakReference<OnStuffHappenedListener>>();
  8.  
  9.     public void addOnStuffHappenedListener(OnStuffHappenedListener l) {
  10.         listeners.add(new WeakReference<OnStuffHappenedListener>(l));
  11.     }
  12. }

An object should always access its listeners via WeakReferences. Otherwise it may unnecessarily prolong the lifetime of not only listeners themselves, but also (and more importantly) their outer objects and everything referenced by them. In general, any scenario involving (non-static) inner classes is potentially suspicious, because of hidden strong reference to the enclosing object that is maintained by the inner one. But listeners are especially dreadful: they are ubiquitous in almost any non-trivial Java code, and often reside within big objects with lot of outbound references – such as UI views and windows.

When we keep only weak references to our listeners, we don’t prevent them (and everything they refer to) from being garbage collected. As a result, we might sometimes encounter a broken link on the list – and this is absolutely fine. The whole point of listeners is to not be concerned about who exactly gets notified on our events. Therefore, a death of recipient is of little interest for us; we simply get rid of it and move on, since that’s how the matters are in the cruel and uncaring world of Java:

  1. private void dispatchOnStuffHappened() {
  2.     Iterator<?> it = listeners.iterator();
  3.     while (it.hasNext()) {
  4.         OnStuffHappenedListener l = it.next().get();
  5.         if (l != null) l.onStuffHappened();
  6.         else           it.remove();
  7.     }
  8. }

Softening the blow

Admittedly, a WeakReference can sometimes be too weak. When it comes to keeping an object alive, it has no power whatsoever. There are circumstances where we’d want to be somewhat less modest and have the object stick around for quite longer than just the next GC sweep – but not necessarily forever. Such requirements are often about fitting inside limited memory space and avoiding OutOfMemoryErrors. If that’s indeed the case, a SoftReference can be a good choice.

Usage of soft references includes rudimentary cache, where we are keeping an object that is generally expected to exist, but can be evicted due to memory pressure. A simple example could be an automatic memoization of function’s result:
import java.lang.ref.SoftReference;
import java.util.List;
import java.util.ArrayList;
import java.math.BigDecimal;

public class FibSeries {
private static SoftReference> cache
= new SoftReference>(null);

public static BigDecimal compute(int x) {
List c = cache.get();
if (c == null) {
c = new ArrayList(x);
c.add(new BigDecimal(0)); c.add(new BigDecimal(1));
cache = new SoftReference>(c);
}

for (int n = c.size(); n <= x; ++n) c.add(c.get(n - 1).add(c.get(n - 2))); return c.get(x); } }[/java] Due to exponential growth of Fibonacci series, size of cache can grow pretty large even for relatively small values of x. That’s why it’s kept behind a SoftReference. In between calls to FibSeries.compute, the list can be reclaimed by garbage collector, should it run into a low memory condition. We are prepared to handle that by recreating the list and once again filling it with values.

Note than in general, you should not rely too much on strength of soft reference. As a rule of thumb, a program should still behave correctly even if we replaced all occurrences of SoftReference with WeakReference (although its performance might suffer, obviously). In practice, VMs keep soft referenced objects alive unless memory becomes an issue, but assuming any specific policy in this regard reduces portability of your code.

post.finalize();

I’m intentionally omitting the third reference type to be found inside java.lang.ref package – that is, the PhantomReference. That’s because its usage is tied to object’s finalization, which is something we care about only in special circumstances (such a JNI interoperability). While hardly magic, they are simply of no use in vast majority of code you’ll ever write.

The other two types, on the other hand, are much more practical – and sometimes simply necessary. And even if lack of WeakReference is not as severe as misplaced delete or free in language without GC, it shows that automatic memory management is not a magic bullet. We still need to know what we are doing.

Tags: , , ,
Author: Xion, posted under Programming »


One comment for post “About Java references”.
  1. Liosan:
    December 21st, 2011 o 12:11

    Great! Wish I new about this earlier.

Comments are disabled.
 


© 2017 Karol Kuczmarski "Xion". Layout by Urszulka. Powered by WordPress with QuickLaTeX.com.