When it comes to testing how you can be sure that the cache releases objects when the VM runs low on memory? This small writing gives some tips about how the running out of memory can be simulated in your test cases safely.

Sometimes it’s necessary to test how the caches behave when application is running low on memory. For example, imagine that in your application you need to load icons from disk. Pretty easy to imagine, right? The first thing you will think about is that you need sort of a memory cache for them to avoid loading again in again from disk, but take them from memory instead. It will be much faster. The main issue with this is that it takes some memory to store the images there and this consumption of memory might become a problem at some moment. Next thing which comes to mind is making the cache to be “soft”. This way your images will be removed one-by-one to release more memory before OutOfMemoryError will be thrown. Well, enough theory… There are millions of other situations where you need similar approach to cache things.

The purpose of this short writing is to show how to test the cache unit and see whether it really releases the memory on demand.

public void TestCache extends TestCase
{
    public void testStress()
    {
        Cache cache = new Cache();

        int entries = 100;
        for (int i = 0; i < entries; i++) cache.put(i, "test");

        int active = 0;
        for (int i = 0; i < entries; i++) if (cache.get(i) != null) active++;

        assertTrue("Cache hasn't released entries.", active < entries);
    }
}

In this code sample I create an abstract Cache instance and populate it with 100 String pairs. Then I look through it and count objects which are still there. The test will most probably fail. Why? Because there was enough memory to hold all of the entries before the final check and GC hasn’t removed the entries from the cache. Got the idea?

Now I wish to show a small trick to force GC to review the memory and remove everything what can be removed (including cached entries). You must be waiting for Runtime.getRuntime().gc(). Here comes the previous piece of code again. Noticed the difference?

public void TestCache extends TestCase
{
    public void testStress()
    {
        Cache cache = new Cache();

        int entries = 100;
        for (int i = 0; i < entries; i++) cache.put(i, "test");

        // Consume all memory
        try
        {
            byte[] buf = new byte[(int)Runtime.getRuntime().maxMemory()];
        } catch (OutOfMemoryError e) {}


        int active = 0;
        for (int i = 0; i < entries; i++) if (cache.get(i) != null) active++;

        assertTrue("Cache hasn't released entries.", active < entries);
    }
}

It is 100% reliable way to consume every byte of free memory and release all objects in the object tree. So the GC will remove everything except live objects before throwing OutOfMemoryError. It will throw this error because it cannot give you whole possible memory just because this very piece of code also requires some, but the goal will be established. Another place to pay attention to is:

byte[] buf = new byte[(int)Runtime.getRuntime().maxMemory()];

This assignment to local variable, though it isn’t used, is vital. If you will not assign the allocated memory to anything local the memory will not be allocated.

This article was very short, but I think that its usefulness didn’t suffer from that.