Over t
I go with door #2: This is not a real leak.
More specifically, it's just related to garbage collection. Three clues:
The path to GC root ends at FinalizerReference, which is closely related to GC. It basically handles calling the finalize()
methods on objects that are eligible for GC -- for which there is one here, namely the ViewRootImpl.WindowInputEventReceiver
instance, which extends InputEventReceiver
, which does have a finalize()
method. If this was a "real" memory leak, then the object would not be eligible for GC, and there should be at least one other path to a GC root.
At least in my test case, if I force GC before taking the heap snapshot, then there is only one reference to MainActivity
(whereas there are two if I take the snapshot without doing so). Looks like forcing GC from DDMS actually includes calling all finalizers (most likely by calling FinalizerReference.finalizeAllEnqueued()
which should release all these references.
I could reproduce it in a device with Android 4.4.4, but not in Android L which has a new GC algorithm (admittedly this is at most circumstantial evidence, but it's consistent with the others).
Why does this happen for some activities and not for others? While I cannot say for sure, it's likely that constructing a "more complicated" activity fires GC (simply because it needs to allocate more memory) while a simple one like this one "generally" does not. But this should be variable.
Why does StrictMode think otherwise?
There are extensive comments in StrictMode
about this case, check the source code of decrementExpectedActivityCount()
. Nevertheless, it looks like it's not working exactly as they intended.
// Note: adding 1 here to give some breathing room during
// orientation changes. (shouldn't be necessary, though?)
limit = newExpected + 1;
...
// Quick check.
int actual = InstanceTracker.getInstanceCount(klass);
if (actual <= limit) {
return;
}
// Do a GC and explicit count to double-check.
// This is the work that we are trying to avoid by tracking the object instances
// explicity. Running an explicit GC can be expensive (80ms) and so can walking
// the heap to count instance (30ms). This extra work can make the system feel
// noticeably less responsive during orientation changes when activities are
// being restarted. Granted, it is only a problem when StrictMode is enabled
// but it is annoying.
Runtime.getRuntime().gc();
long instances = VMDebug.countInstancesOfClass(klass, false);
if (instances > limit) {
Throwable tr = new InstanceCountViolation(klass, instances, limit);
onVmPolicyViolation(tr.getMessage(), tr);
}
Update
Actually, I've performed more tests, calling StrictMode.incrementExpectedActivityCount()
using reflection, and I've found a very curious result, which does not change the answer (it's still #2) but I think provides an additional clue. If you increase the number of "expected" instances of the Activity (say, to 4), then the strict mode violation will still occur (claiming 5 instances are present), on every 4th rotation.
From this I'm led to conclude that the call to Runtime.getRuntime().gc()
is what actually releases these finalizable objects, and that code runs only after going beyond the set limit.
What if any action can be taken to fix it?
While it's not 100% foolproof, calling System.gc()
in the Activity's onCreate()
is likely to get this problem to go away (and it did in my tests). However, the specification for Java clearly says that garbage collection cannot be forced (and this is merely a hint) so I'm not sure if I'd trust going with the death penalty, even with this "fix"...
You could possibly combine it with manually increasing the limit for activity instance count by calling reflection. But it seems like a really crude hack:
Method m = StrictMode.class.getMethod("incrementExpectedActivityCount", Class.class);
m.invoke(null, MainActivity.class);
(Note: be sure to do this only once at application startup).
I am not an Android developer so this is a bit of a shot in the dark.
Presumably since the behaviour is not consistent for other activities, but is triggered nonetheless by a completely trivial one, the issue is down to the time taken for onCreate to complete.
If onCreate can complete before the pause/stop/destroy sequence is finished (and eventual garbage collection though it need not get quite to that point afaics) for the old activity then there really will be two instances present.
A simple solution to prevent this is for the activity to have a static atomicboolean "ready" (initially false) that check before doing anything else in onCreate where you'd loop
while(!ready.compareAndSet(false, true)) {
//sleep a bit
}
then override the ondestroy lifecycle callback and invoke ready.compareAndSet(true, false)
Apologies if this approach is utterly naive.
I want to summarize and provide the final answer to safe time for other developers.
Is android.os.StrictMode$InstanceCountViolation a problem or not
This can be a real problem. To identify if this is a problem I can recommend the following post: Detecting leaked Activities in Android. If you will see that there are objects holding a reference to this activity which don't related to Android Framework then you have a problem which should be fixed by you.
In case there are no objects holding a reference to this activity which don't related to Android Framework than it means that you encountered with the problem related to how detectActivityLeaks check is implemented. Also as was pointed the garbage collection should help in this case
Why detectActivityLeaks works incorrectly and reproduces not on all devices
If we will look at sources of the detectActivityLeaks:
// Note: adding 1 here to give some breathing room during
// orientation changes. (shouldn't be necessary, though?)
limit = newExpected + 1;
...
// Quick check.
int actual = InstanceTracker.getInstanceCount(klass);
if (actual <= limit) {
return;
}
// Do a GC and explicit count to double-check.
// This is the work that we are trying to avoid by tracking the object instances
// explicity. Running an explicit GC can be expensive (80ms) and so can walking
// the heap to count instance (30ms). This extra work can make the system feel
// noticeably less responsive during orientation changes when activities are
// being restarted. Granted, it is only a problem when StrictMode is enabled
// but it is annoying.
Runtime.getRuntime().gc();
long instances = VMDebug.countInstancesOfClass(klass, false);
if (instances > limit) {
Throwable tr = new InstanceCountViolation(klass, instances, limit);
onVmPolicyViolation(tr.getMessage(), tr);
}
The problem here is that to count instances correctly, all previous ones should be garbage collected (at least logic is relay on this). And this is the main problem of this implementation. The reason why is that one round trip of the GC can't be enough in Java if you want to be sure that all objects which are ready to be released are collected by GC. In most cases it is enough to call System.gc() twice or use something similar to methods implemented in this jlibs library.
That's why this issue reproduces on some devices (OS versions) and doesn't reproduce on another ones. This all depends on how GC is implemented and sometimes only one call is enough to be sure that object will be collected by GC.
How to avoid this issue in case there is no leaked Activities I simply run System.gc() before starting activity in debug configuration like in the following example. This will help to avoid this problem and gives you ability to continue using detectActivityLeaks check.
if (BuildConfig.DEBUG)
{
System.gc();
}
Intent intent = new Intent(context, SomeActivity.class);
this.startActivity(intent);
This also ensures that in release build Garbage Collection is not forced as this is not recommended.