Monday, February 16, 2009

System.gc Even More Harmful A Guide To WeakReferences

I wrote a couple of times about how problematic System.gc is for mobile developers, I'd like to drive that point further today.
LWUIT started making heavy use of WeakReference's in the past few months, which is something I've been using for a while now on SE but was reluctant to go for in ME. Not anymore, they solve allot of problems in the constrained device area and make our lives much easier.

They do have one drawback System.gc() breaks them, but I digress. First lets go over what a WeakReference is since I find that even experienced Java programmers are unfamiliar with this remarkably useful tool.

WeakReferences are a part of JavaSE since Java 2 (JDK 1.2) and a part of CLDC 1.1 and higher. A weak reference is a remarkably simple object it just includes a get() method returning a different object... That in itself is not so useful, however it is useful since the WeakReference has special treatment by the garbage collector in Java.

The Java garbage collector works by traversing the heap and "marking" every object it encounters in the object graph (everything to which we have a reference/pointer). Everything that is "unmarked" is potential garbage.
When walking across the heap if the garbage collector encounters a weak reference it will not traverse further, so unless you have an actual pointer to the content in the other side of the reference it can be collected if necessary.

This is very useful for caching which is probably the best performance optimization you can perform, by keeping a weak reference to data you can reuse an existing object/resource but allow it to be removed when you run out of space.

E.g. In LWUIT the bitmap fonts used to always cache two colors to support the common case of selected/unselected color switching.
This is good, but might be wasteful for cases where you have a unique font for the title which will never change its color... By leveraging weak references we can cache more than 2 colors easily (for special cases) while releasing redundant data for fonts that only use one color. All of this is handled seamlessly by the VM.

How does this work?

To use a weak reference just use:
myRef = new WeakReference(myObject);

Later on when you need to use your object you will need to extract it from the reference:
MyObjectType myInstance = (MyObjectType)myRef.get();
if(myInstance == null) {
// need to recreate and cache the object
myInstance = create();
myRef = new WeakReference(myInstance);
// use myInstance

Notice that when myInstance is assigned you have a "real" pointer to the object, as long as that exists it doesn't matter if you have or don't have a weak reference the object won't be collected.

This brings me back to System.gc(), it will eliminate all cached objects whether needed or not hence harming application performance further. We eliminated all of LWUIT's internal calls to System.gc (that are not related to exceptions) to avoid this sort of problem and we recommend everyone does the same to maximize font and image scaling performance.

One last tip which is a common question we get allot. Resource files often contain multiple images, caching them can become very expensive. However, opening a resource file must read the whole file since CLDC has no ability to seek an offset within files...
Assuming you want only some images from a resource file you can keep the resource file in a weak reference and keep a Hashtable of image names to weak refrences for images. This allows you to lazily load a resource file and featch only the necessary images but still allows the GC to remove unnecessary images.

1 comment:

  1. Hi.
    This is indeed a problem, and the reason for that is that a WeakReference is not made for caching - a SoftReference is. WeakReference semantics state that as soon (i.e. immediately) as an object is weakly reachable (i.e. reachable only via weak references) it is collected by the GC, regardless of available heap space. This can happen not only as a result of a System.gc call, but even as a result of a simple "new" allocation that triggers garbage collection. This behavior is useful in many cases when associating two objects and wanting them to disappear at the same time (that's precisely what WeakHashMap in SE is for). If you allocate an object and only keep a WeakReference to it (without some other object strongly-referencing it as well) it's as if you've never created the object at all. Poof - it's gone.
    A SoftReference, on the other hand, was made for caching. It is similar to a WeakReference, only that the GC does not have to deallocate the object it points to immediately, but only when memory is low.
    This is why SoftReferences are especially valuable in domains constricted by both CPU+IO (which makes caching necessary), and memory (which makes cache cleanup necessary). In fact, I would say that soft references are much more valuable for JavaME than WeakReferences. Unfortunately - they don't exist in CLDC.
    I'll try to think of a way to simulate them (perhaps using an LRU map, like LinkedHashMap in SE).