Search code examples
javagarbage-collectionsoft-references

Java SoftReference: Soft references were not collected before the occurrence of OOM


it's my test code

public class Test {

    public static SoftReference<byte[]> cache = new SoftReference<>(new byte[0]);

    public static List<byte[]> list = new ArrayList<>();

    public static void main(String[] args) {
        try {
            func();
        } catch (OutOfMemoryError e) {
            sniff();
            e.printStackTrace();
        }
    }

    public static void func() {
        byte[] bytes = new byte[1024 * 1024];
        cache = new SoftReference<>(bytes);
        for(;;) {
            byte[] tmp = new byte[1024 * 1024];
            list.add(tmp);
        }
    }

    public static void sniff() {
        byte[] bytes = cache.get();
        if (bytes == null) {
            System.out.println("recycling data.");
        } else {
            System.out.println("object still live");
        }
    }
}

The program output is as follows

object still live

I don't understand? why? This is a sentence I found in the official documentation of Oracle:

All soft references to softly-reachable objects are guaranteed to have been cleared before the virtual machine throws an OutOfMemoryError.

Even more strangely, if I put byte[] bytes = new byte[1024 * 1024]; cache = new SoftReference<>(bytes); into the for loop; like this

public class Test {

    public static SoftReference<byte[]> cache = new SoftReference<>(new byte[0]);

    public static List<byte[]> list = new ArrayList<>();

    public static void main(String[] args) {
        try {
            func();
        } catch (OutOfMemoryError e) {
            sniff();
            e.printStackTrace();
        }
    }

    public static void func() {
        for(;;) {
            byte[] tmp = new byte[1024 * 1024];
            list.add(tmp);
            byte[] bytes = new byte[1024 * 1024];
            cache = new SoftReference<>(bytes);
        }
    }

    public static void sniff() {
        byte[] bytes = cache.get();
        if (bytes == null) {
            System.out.println("recycling data.");
        } else {
            System.out.println("object still live");
        }
    }
}

the program output is as follows:

recycling data.

I have two question:

  1. The first writing method,Why did the garbage collector not collect SoftReferences
  2. Why do these two writing methods produce such a difference

I analyzed the dump file and the first way of writing really doesn't collect it


Solution

  • The important thing to note in the quote from the official documentation is in bold below:

    All soft references to softly-reachable objects are guaranteed to have been cleared before the virtual machine throws an OutOfMemoryError.

    Softly-reachable objects are those that can be reached by the "root" context only through soft references or weaker (reference).

    In your first example you are putting your byte[] in a List; this creates a path of strong references to the object - that is why it is not garbage collected in the end.

    In your second example, the array referenced by the tmp variable is added to the list and lives at least as long as the list lives. The array referenced by the bytes variable however is created and is strongly referenced in the scope of func() only. The only reference that escapes is the soft reference. So, before the OutOfMemoryError it is guaranteed that this softly reachable array, the last you created in the loop, will be garbage collected. The ones you created in the loop iterations before the last are already unreachable, because even the soft reference to them that survived the loop is overwritten in the next iteration.

    You could try changing the list to be List<SoftReference<byte[]>>; you will find out that all of the soft references will have been cleared before the OOME.