RamEater released
I have been trying to work on an interesting Android development problem recently. The activity lifecycle includes a number of less obvious states. If the device’s memory is under pressure then an activity can be unloaded, and ultimately the activity and the application process can be unloaded.
As a developer it is possible to simulate the activity being unloaded by setting the developer option “Don’t Keep Activities” like this.
I needed an easy mechanism to force the application process to unload by allocating all the available memory. It was pretty easy to put together an app to do this, the source code is in Github and the app is in the play store.
There were a couple of learning points this I discovered. It was pretty simple to get an app that would spin off multiple services and each service would allocate all the memory it was allowed to. However the call that gets the maximum amount of memory allowed (activityManager.getLargeMemoryClass()) is only supported on Android 3 and above. Also it returns an amount in MB and if you work out the amount of memory to allocate by assuming that 1MB = 1024 * 1024 Bytes then the allocation fails. I think this is because the number returned is 1000 * 1024 Bytes however other reasons are possible so I coded it such that it tries to allocate less and less memory until it succeeds. Like so
private void eatAllMemory() { //Get amount of memory this app is allowed to use (in MBs) int availMem = activityManager.getLargeMemoryClass(); eatMemory(availMem); } private void eatMemory(int numberOfMb) { // convert MB -> KB -> Bytes int numberOfBytes = numberOfMb * 1024 * 1024; boolean memoryAllocated = false; String message = (numberOfBytes / 1024 / 1024) + " MB allocated"; while (!memoryAllocated) { try { // we need to do this before we allocate all the memory :-) message = (numberOfBytes / 1024 / 1024) + " MB allocated"; // char is 2 bytes in java memoryBlackHole = new char[numberOfBytes / 2]; memoryAllocated = true; } catch (OutOfMemoryError e) { // we cannot have that much, lets try 1MB less numberOfBytes = numberOfBytes - (1024 * 1024); } } fillBlackHole(numberOfBytes / 2); updateNotification(message); }
Also when I used the memory profiler in Android Studio (which gave another slightly different amount of memory allocated) I noticed that when I used a device using the Dalvik runtime that the memory was allocated immediately, however on a device using Art runtime the memory was not allocated. It turned out that I needed to write to the memory before it was allocated.
private void fillBlackHole(int numberOfChars) { // dalvik allocate the memory when requested // art is clever and waits until you use the memory for (int index=0; index < numberOfChars; index++) { memoryBlackHole[index] = 'a'; } }
The memory itself is allocated into separate services for two reasons, one that the process was less likely to be killed of (after all we want the app under development top be killed off) and also because we need to run each in its own process so each can allocate the maximum allowed memory.
Another piece of the puzzle is that its unclear from the documentation if the amount of memory that can be allocated is restricted by application or by process, it turned out that if the memory allocation service are run in their own processes by setting the process in the manifest file like this they work fine
<service android:name="Service1" android:process=":sp1"> </service> <service android:name="Service2" android:process=":sp2"> </service>
I have found using the tool has made it much easier to trigger low memory situations.