Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android: the GC doesn't respect SoftReferences?

It seams that Dalvik's garbage collector doesn't respect SoftReferences and removes them as soon as possible, just like WeakReferences. I'm not 100% sure yet, but despite the fact that there is still ~3MB of free memory my SoftReferences get cleared after I see "GC freed bla-bla-bla bytes" in LogCat.

Also, I saw a comment by Mark Murphy here:

Except that it doesn't work on Android, at least in the 1.5 timeframe. I have no idea if the GC SoftReference bugs have been fixed. SoftReferences get GC'd too soon with this bug.

Is it true? Are SoftReferences not respected?

How to workaround this?

like image 746
JBM Avatar asked Oct 25 '10 11:10

JBM


3 Answers

After not receiving an answer I decided to make my own study. I've made a simple test to exercise the GC against SoftReferences.

public class TestSoftReference extends TestCase {

    public void testSoftRefsAgainstGc_1() {        testGcWithSoftRefs(1);    }

    public void testSoftRefsAgainstGc_2() {        testGcWithSoftRefs(2);    }

    public void testSoftRefsAgainstGc_3() {        testGcWithSoftRefs(3);    }

    public void testSoftRefsAgainstGc_4() {        testGcWithSoftRefs(4);    }

    public void testSoftRefsAgainstGc_5() {        testGcWithSoftRefs(5);    }

    public void testSoftRefsAgainstGc_6() {        testGcWithSoftRefs(6);    }

    public void testSoftRefsAgainstGc_7() {        testGcWithSoftRefs(7);    }


    private static final int SR_COUNT = 1000;

    private void testGcWithSoftRefs(final int gc_count) {
        /* "Integer(i)" is a referrent. It is important to have it referenced
         * only from the SoftReference and from nothing else. */
        final ArrayList<SoftReference<Integer>> list = new ArrayList<SoftReference<Integer>>(SR_COUNT);
        for (int i = 0; i < SR_COUNT; ++i) {
            list.add(new SoftReference<Integer>(new Integer(i)));
        }

        /* Test */
        for (int i = 0; i < gc_count; ++i) {
            System.gc();

            try {
                Thread.sleep(200);
            } catch (final InterruptedException e) {
            }
        }

        /* Check */
        int dead = 0;
        for (final SoftReference<Integer> ref : list) {
            if (ref.get() == null) {
                ++dead;
            }
        }
        assertEquals(0, dead);
    }
}

The idea is that I make few runs of the same code increasing stress on SoftReferences each time (by running more GC passes).

Results are pretty interesting: All runs pass just fine except for one!

On Android 1.5 device:
testSoftRefsAgainstGc_1()  FAILED!  AssertionFailedError: expected:0 but was:499
testSoftRefsAgainstGc_2()  passed
testSoftRefsAgainstGc_3()  passed
testSoftRefsAgainstGc_4()  passed
testSoftRefsAgainstGc_5()  passed
testSoftRefsAgainstGc_6()  passed
testSoftRefsAgainstGc_7()  passed


On Android 1.6 device:
testSoftRefsAgainstGc_1()  passed
testSoftRefsAgainstGc_2()  FAILED!  AssertionFailedError: expected:0 but was:499
testSoftRefsAgainstGc_3()  passed
testSoftRefsAgainstGc_4()  passed
testSoftRefsAgainstGc_5()  passed
testSoftRefsAgainstGc_6()  passed
testSoftRefsAgainstGc_7()  passed

On Android 2.2 device:
All pass.

These test results are stable. I've tried many times and every time it is the same. So I believe it is indeed a bug in garbage collector.

CONCLUSION

So, what we learn out of this... Using SoftReferences in your code is pointless for Android 1.5-1.6 devices. For these devices you will not get the behavior you expect. I didn't try for 2.1, however.

like image 103
JBM Avatar answered Nov 20 '22 08:11

JBM


I reported this issue to Google: https://code.google.com/p/android/issues/detail?id=20015

like image 36
candrews Avatar answered Nov 20 '22 07:11

candrews


@JBM I've tried your TestCase on Nexus S (android4.2.2), all tests are failed. GC is more aggressive against SoftReference on android4.2.2

like image 20
shaobin0604 Avatar answered Nov 20 '22 08:11

shaobin0604