Java SoftReference guarantee failing
Asked Answered
C

1

6

The JDK 7 documentation has this to say about a SoftReference:

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

However, in my test program, I'm seeing OutOfMemoryError consistently (except for the 'Stranger Behavior' section below):

// RefObjectTest.java

import java.util.*;
import java.lang.ref.*;

public class RefObjectTest {

    public static void main(String[] args) {

        ArrayList<byte[]> leaks = new ArrayList<>();

        byte[] obj = new byte[10 * 1024 * 1024];

        SoftReference<byte[]> ref = new SoftReference<>(obj);

        // WeakReference is supposed to be eagerly GC'ed, but I see no
        // difference in terms of program behavior: still get OOME.
        //
        // WeakReference<byte[]> ref = new WeakReference<>(obj);

        obj = null;

        while(true) {
            byte[] x = ref.get();
            if(x == null) {
                System.out.println("Referent stands garbage collected!!");
                break;
            } else {
                System.out.println("Referent still alive.");
            }

            // Leak memory in small, 10k increments. If soft reference
            // worked the way it's advertized, then just before the OOME, the 
            // 10MB referent should have been GC'ed to accomodate this small
            // 10K new memory request. But it doesn't appear to work that way!

//          try {
                leaks.add(new byte[10 * 1024]);
//          } catch(OutOfMemoryError e) {
//              System.out.println(ref.get() == null ? "Referent cleared" : 
//                  "Referent still alive");
//          }

            // VERY STRANGE: If I re-instate the above try-catch block, then no OOME is
            // thrown, and the program keeps printing "Referent still alive" lines
            // forever until I finally kill it with a Ctrl+C.

            // Uncommenting gc() tends to delay the OOME in terms of time, 
            // but OOME eventually does happen, and after the same number of
            // iterations of this loop. 
            // 
            // System.gc();
        }
    }
}

Here's the output:

$ java -Xmx15m RefObjectTest
Referent still alive.
  ...
Referent still alive.
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
        at RefObjectTest.main(RefObjectTest.java:38)

Stranger Behavior

What is VERY STRANGE is, if I re-instate the try-catch block, the program appears to run fine forever, printing "Referent still alive." lines until I get tired and kill it.

$ java -version
java version "1.7.0_45"
Java(TM) SE Runtime Environment (build 1.7.0_45-b18)
Java HotSpot(TM) 64-Bit Server VM (build 24.45-b08, mixed mode)
$ 
$ uname -a
Linux ida 3.10.11-100.fc18.x86_64 #1 SMP Mon Sep 9 13:06:31 UTC 2013 x86_64 x86_64 x86_64 GNU/Linux

What am I missing in all of this?

Cleaves answered 3/1, 2014 at 10:16 Comment(0)
M
4

It's not strange at all, the allocation is failing which is throwing an exception. By catching the exception you are saying that the program can continue, by not catching the exception you are saying that you can't handle it and the program exits.

Inside your while loop you are doing:

        byte[] x = ref.get();

This is creating a new Strong reference from your Soft Reference. As soon as you do that it is no longer eligible for collection. You don't clear that Strong Reference before you go on to do the new allocation.

Do x = null after you have done the test.

Manolete answered 3/1, 2014 at 10:53 Comment(3)
Thanks for your quick response! (I goofed up even in my test code!)Cleaves
The byte[] obj right at the beginning is another strong reference that would prevent clearing the softreference.Oxytocin
Normally you would be right, but that one gets set to null just before the while(true).Manolete

© 2022 - 2024 — McMap. All rights reserved.