Relevant only in that it motivates eliminating any false positives from strict mode, since the continued presence of any makes the death penalty impractical
Over the last few days I've been chasing down and fixing memory leaks in an application. On getting to the point that I believed I'd fixed them all, I implemented a fail-loud mechanism similar to that described in Android StrictMode and heap dumps (enable instance tracking with death penalty, intercept the shutdown error message, dump heap, send a distress signal next time application starts). All just in debug builds of course.
To the point
Having believed I've fixed all activity leaks, certain activities still result in strict mode instance violation warnings on screen rotation. Curiously it is only some, not all of the application's activities that do this.
I've reviewed heap dumps taken when such violations occur, and reviewed the code of the activities in question in search of the leak, but do not get anywhere.
So at this point I tried to make the smallest possible test case. I create a completely blank activity (no layout even), looking like this:
package com.example.app;
import android.app.Activity;
import android.os.Bundle;
import android.os.StrictMode;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
StrictMode.setVmPolicy(
new StrictMode.VmPolicy.Builder()
.detectAll()
.penaltyLog()
.build());
StrictMode.setThreadPolicy(
new StrictMode.ThreadPolicy.Builder()
.detectAll()
.penaltyDeath()
.penaltyLog()
.build());
super.onCreate(savedInstanceState);
}
}
And for completeness, the manifest:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.app" >
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="com.example.app.MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
I open the activity (device held portrait). I rotate to landscape, and back to portrait, at which point I see from StrictMode this in LogCat:
01-15 17:24:23.248: E/StrictMode(13867): class com.example.app.MainActivity; instances=2; limit=1 01-15 17:24:23.248: E/StrictMode(13867): android.os.StrictMode$InstanceCountViolation: class com.example.app.MainActivity; instances=2; limit=1 01-15 17:24:23.248: E/StrictMode(13867): at android.os.StrictMode.setClassInstanceLimit(StrictMode.java:1)
Heap dump
At this point I manually acquire a heap dump using DDMS. Here are the two instances in MAT:
And here's the one that was leaked, with the first part of the path from it to a GC root:
Note that no matter how many times I rotate between portrait and landscape, the number of actual instances never exceeds two, and the number of expected instances never exceeds one.
Can anyone make sense of the leak? Is it even a real leak? If it is, it presumably must be an Android bug. If it is not, the only other thing I can see that it could be is a bug in StrictMode.
Good answers might include
- If this is a real leak, an explanation of how it occurs, and what if any action can be taken to fix it or suppress StrictMode from initiating the death penalty for such cases (recall that false positives/neutrals make the death penalty impractical).
- If this is not a real leak, an explanation of why StrictMode thinks otherwise, and what if any action can be taken to fix it or suppress StrictMode from initiating the death penalty for such cases (recall that false positives/neutrals make the death penalty impractical).
- In either case, hypotheses as to why this does not occur with all activities (the majority of activities in the application I'm working on do not produce such violations on rotation).
I've got as far as looking at the StrictMode source and see nothing obviously wrong there - as expected, it forces a GC before considering an extra instance to be a violation.
Good entry points for reading the StrictMode source are:
- http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.4_r1/android/os/StrictMode.java#StrictMode.trackActivity%28java.lang.Object%29
- http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.4_r1/android/os/StrictMode.java#StrictMode.InstanceTracker.finalize%28%29
- http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.4_r1/android/os/StrictMode.java#StrictMode.incrementExpectedActivityCount%28java.lang.Class%29
- http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.4_r1/android/os/StrictMode.java#StrictMode.decrementExpectedActivityCount(java.lang.Class)
Full disclosure
I've only done these tests on one device, a Galaxy S4 running CyanogenMod 11 snapshot M2 (http://download.cyanogenmod.org/get/jenkins/53833/cm-11-20140104-SNAPSHOT-M2-jfltexx.zip), but I can't imagine CyanogenMod would deviate from KitKat in terms of activity management.
Additional StrictMode sources:
- Activity's instanceTracker instance is just a final instance field: http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.4_r1/android/app/Activity.java#Activity.0mInstanceTracker
- Where expected activity instance counts are incremented: http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.4_r1/android/app/ActivityThread.java#2095
- Where expected activity instance counts are decremented: http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.4_r1/android/app/ActivityThread.java#3485