About caching and optimizing your application.

Working on the SpreadsheetView taught me that I had to profile my application on a regular basis. In fact, I always discover something.

But it’s not always simple to find bad implementation or memory leaks. For example, I rarely put some images inside my SpreadsheetView. I recently discovered a SpreadsheetView, built by a colleague, with the same image repeated on all rows. And just like that, you take up to 35% of your time fetching and displaying images!

I realized it was a scenario I did not anticipated, hence the importance of testing. Anyway, I obviously decided to cache these images and started with a simple :

HashMap<String, Image> imageMap;

But I was immediately confronted with a second issue : Memory.

Customers were constantly displaying different grids with different large images, and before I knew it, my application took way too much memory.

And there comes our savior the WeakReference.

Using a WeakReference for my Map allow me to cache anything and increase my application performance, but also let that Map be garbage collected if necessary. Thus preserving memory. It’s a compromise that kinda work nice. So how does it look?

It’s really simple. All the images are stored in the Map, the only thing we need to be careful is how to access it. You need to respect encapsulation by keeping your map private, and always access it via its method. That method will then look if the Map is still here. If it is, simply return it. If not (garbage collected), recreate it and return it!

private Reference<HashMap<Double, Image>> imageMap;

protected HashMap<Double, Image> getImageMap() {
        if (imageMap == null || imageMap.get() == null) {
            HashMap<Double, Image> map = new HashMap<>();
            imageMap = new WeakReference<>(map);
            return map;
        }
        return imageMap.get();
    }

Now you can be sure you won’t be looking over and over for the same image, and you won’t trigger OutOfMemoryError.

Please note that if you are in a multi-threaded environment, you need to be more careful. You can declare this method synchronized.

Or you can add some spice and use the Double-checked locking design pattern!
It should look like that if I’m not mistaken :

protected HashMap<Double, Image> getImageMap() {
        if (imageMap == null || imageMap.get() == null) {
            synchronized (this) {
                if (imageMap == null || imageMap.get() == null) {
                    HashMap<Double, Image> map = new HashMap<>();
                    imageMap = new WeakReference<>(map);
                    return map;
                }
            }
        }
        return imageMap.get();
    }

I use that in quite a few strategic places and was able to reduce image fetch to less than 1%.

2 thoughts on “About caching and optimizing your application.”

  1. Hi Sam,

    thanks for the blog post!

    However, I think that you could even improve the cache by having the value of the Map as a WeakReference, not the Map itself, i.e. HashMap<Double, WeakReference>.

    This should enable the GC to collect single unused images while still keeping your cache.
    The difference would be in accessing these images: you would need to check if the image is still there; if not, you have to reload it and put it in the map again.

    Or have you tried this before?

    Cheers,
    Patrick

    1. Hi Patrick,

      Thanks for the comment! I think it’s a very good idea indeed. I will try to implement it, and see how it behaves. If it’s better, I’ll modify this post 😉

      Thanks again for the tip,
      Sam’

Leave a Reply

Your email address will not be published. Required fields are marked *