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?
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.
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.
I reported this issue to Google: https://code.google.com/p/android/issues/detail?id=20015
@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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With