o7planning

Java SoftReference Tutorial with Examples

  1. SoftReference
  2. SoftReference(T, ReferenceQueue<? super T>
  3. Caching example

1. SoftReference

java.lang.ref.SoftReference class is used to create an object that wraps another object - innerObject. The object it wraps can be removed from memory by Garbage Collector (GC) if it is no longer being used somewhere else stronger than GC and the system is in need of more memory.
The object is wrapped inside a WeakReference that acts like a diner in a restaurant. When diners finish eating, they are ready to leave the table even if at that time the restaurant has many empty tables. SoftReference is a bit different from WeakReference, diners can sit back and only leave if the restaurant has no more free tables or the number of available free tables is less than a safe value.
Basically, it's a bit more difficult to give an example of SoftReference and show you firsthand how Garbage Collector removes soft references from memory, since we need to simulate near-full memory situation. If the simulation is not perfect we will overflow the memory. Although before OutOfMemoryError was thrown, soft references were removed from memory.
Advice: WeakReference should be learned before continuing with SoftReference:
When an object is removed from memory, its finalize() method is called. Note: This method has been marked as deprecated since Java 9, but we can still use it just to print a message.
AnyObject.java
package org.o7planning.beans;
 
public class AnyObject {
    private String val;
    public AnyObject(String val) {
        this.val = val;
    }
    public String getVal() {
        return this.val;
    }
    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("I am being removed from memory");
    }
}
The example below shows that GC will remove soft references from memory to avoid throwing an OutOfMemoryError.
SoftReference_obj_ex1.java
package org.o7planning.softreference.ex;

import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.List;

import org.o7planning.beans.AnyObject;

public class SoftReference_obj_ex1 {

    public static void main(String[] args) throws InterruptedException {
        // Create myAnyObject reference points to AnyObject("Obj1").
        AnyObject myAnyObject = new AnyObject("Obj1");

        // Create SoftReference object
        SoftReference<AnyObject> softRef = new SoftReference<AnyObject>(myAnyObject);

        System.out.println("softRef.get(): " + softRef.get());

        List<String> list= new ArrayList<String>();

        int i = 0;
        String s = "";
        while (true) {
            AnyObject innerObject = softRef.get();
            if (innerObject == null) {
                System.out.println("Inner object is removed by Garbage Collector");
                System.out.println("softRef.get(): " + innerObject);
                break;
            }
            i++;
            //
            s = s + " String " + i; // Throw OutOfMemoryError
            list.add(s);
            System.out.println("Create new String: " + i);
        }
    }
}
Output:
softRef.get(): org.o7planning.beans.AnyObject@5e91993f
Create new String: 1
Create new String: 2

...

Create new String: 24952
Create new String: 24953
Create new String: 24954
Exception in thread "main" I am being removed from memory
java.lang.OutOfMemoryError: Java heap space
    at java.base/java.util.Arrays.copyOfRange(Arrays.java:4030)
    at java.base/java.lang.StringLatin1.newString(StringLatin1.java:715)
    at java.base/java.lang.StringBuilder.toString(StringBuilder.java:448)
    at org.o7planning.softreference.ex.SoftReference_obj_ex1.main(SoftReference_obj_ex1.java:33)
Java allows you to configure when GC should remove soft references from memory.
-XX:SoftRefLRUPolicyMSPerMB=1000
The default value of SoftRefLRUPolicyMSPerMB parameter is 1000 Milliseconds. This means that if only 10MB of HEAP memory is available, GC will work to remove soft references that have been unused for longer than 1000 milliseconds.
Java documentation says that:
"All soft references to softly-reachable objects are guaranteed to have been freed before the virtual machine throws an OutOfMemoryError."
Apparently the above is only really true when "-XX:SoftRefLRUPolicyMSPerMB=0".
Another better example. Let's try to simulate a situation where Java's memory is near exhaustion:
SoftReference_obj_ex2.java
package org.o7planning.softreference.ex;

import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.List;

import org.o7planning.beans.AnyObject;

public class SoftReference_obj_ex2 {

    public static void main(String[] args) throws InterruptedException {
        // A String has size of 1Mb.
        String oneMbString = create1MbString();

        // Create myAnyObject (~2 Mb in HEAP).
        AnyObject myAnyObject = new AnyObject(oneMbString + oneMbString);

        // Create SoftReference object
        SoftReference<AnyObject> softRef = new SoftReference<AnyObject>(myAnyObject);

        System.out.println("softRef.get(): " + softRef.get());
        myAnyObject = null;

        List<String> list = new ArrayList<String>();

        int i = 0;

        while (true) {
            i++;
            long freeMemoryInBytes = Runtime.getRuntime().freeMemory(); // in bytes
            long freeMemoryInMbs = freeMemoryInBytes / (1024 * 1024);
            System.out.println("Free memory in Mb: " + freeMemoryInMbs);

            //
            if (freeMemoryInMbs <= 10) {
                Thread.sleep(1200);
            }
            if (freeMemoryInMbs <= 2) {
                System.out.println("Done!");
                break;
            }
            System.out.println(" >> Create new String");
            String s = oneMbString + " - " + i;
            list.add(s);
        }
    }

    // Create a String has the size of 1MB.
    // 1MB = 1024KB = 1024x1024 bytes = (2^10)*(2^10) bytes = 2^20 bytes.
    private static String create1MbString() {
        String s = "A"; // 2 bytes
        for (int i = 0; i < 20; i++) {
            s = s + s;
        }
        return s;
    }
}
Output:
softRef.get(): org.o7planning.beans.AnyObject@5e91993f
Free memory in Mb: 238
 >> Create new String

...

Free memory in Mb: 16
>> Create new String
Free memory in Mb: 12
>> Create new String
Free memory in Mb: 8
>> Create new String
Free memory in Mb: 14
>> Create new String
Free memory in Mb: 10
>> Create new String
Free memory in Mb: 10
>> Create new String
Free memory in Mb: 8
>> Create new String
Free memory in Mb: 6
>> Create new String
Free memory in Mb: 4
I am being removed from memory
>> Create new String
Free memory in Mb: 2
Done!
SoftReference constructors
SoftReference(T referent)

SoftReference(T referent, ReferenceQueue<? super T> queue)
All methods of SoftReference are inherited from the parent class.
// Methods inherited from parent.
public T get()  
public void clear()   
public boolean isEnqueued()   
public boolean enqueue()

2. SoftReference(T, ReferenceQueue<? super T>

Create a SoftReference object that wraps innerObject object. When innerObject is removed from memory by GC, this SoftReference object will be added to the queue.
SoftReference(T innerObject, ReferenceQueue<? super T> queue)

3. Caching example

Sometimes SoftReference is also used in a simple cache system, where the data rarely changes. Java virtual machine automatically removes these data when it needs more memory.
Example: A cache system stores data of image files.
ImageCache.java
package org.o7planning.softreference.cache.ex;

import java.lang.ref.SoftReference;
import java.util.HashMap;
import java.util.Map;

public class ImageCache {
    private static final ImageCache instance = new ImageCache();

    private Map<String, SoftReference<byte[]>> cacheMap = new HashMap<>();

    private ImageCache() {
    }

    public static ImageCache getInstance() {
        return instance;
    }

    public byte[] getImageData(String imagePath) {
        SoftReference<byte[]> value = this.cacheMap.get(imagePath);
        
        byte[] data = null;
        if(value == null || (data = value.get()) == null)  {
            data = this.readImageFromDisk(imagePath);
            this.cacheMap.put(imagePath, new SoftReference<byte[]>(data));
            System.out.println(">> Load data from disk: " + imagePath);
        } else  {  
            System.out.println("   Found data in cache: " + imagePath);
        }  
        return data;
    }

    private byte[] readImageFromDisk(String imagePath) {
        // Read from disk..
        return new byte[3];
    }
}
ImageCacheTest.java
package org.o7planning.softreference.cache.ex;

public class ImageCacheTest {

    public static void main(String[] args) throws InterruptedException {
        String[] imagePaths = new String[] { //
                "image1.png", //
                "image1.png", //
                "image2.png", //
                "image2.png", //
                "image1.png", //
                "image3.png", //
                "image3.png", //
                "image1.png", //
        };

        ImageCache cache = ImageCache.getInstance();

        for (int i = 0; i < imagePaths.length; i++) {
            byte[] data = cache.getImageData(imagePaths[i]);
            Thread.sleep(500);
        }

        System.out.println("Done!");
    }
}
Output:
>> Load data from disk: image1.png
   Found data in cache: image1.png
>> Load data from disk: image2.png
   Found data in cache: image2.png
   Found data in cache: image1.png
>> Load data from disk: image3.png
   Found data in cache: image3.png
   Found data in cache: image1.png
Done!

Java Basic

Show More