In a hunt for a memory-leak in my app I chased down a behaviour I can't understand. I allocate a large memory block, but it doesn't get garbage-collected resulting in a OOM, unless I explicit null the reference in onDestroy.
In this example I have two almost identical activities that switch between each others. Both have a single button. On pressing the button MainActivity starts OOMActivity and OOMActivity returns by calling finish(). After pressing the buttons a few times, Android throws a OOMException.
If i add the the onDestroy to OOMActivity and explicit null the reference to the memory chunk, I can see in the log that the memory is correctly freed.
Why doesn't the memory get freed automatically without the nulling?
MainActivity:
package com.example.oom;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
public class MainActivity extends Activity implements OnClickListener {
private int buttonId;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
System.gc();
Button OOMButton = new Button(this);
OOMButton.setText("OOM");
buttonId = OOMButton.getId();
setContentView(OOMButton);
OOMButton.setOnClickListener(this);
}
@Override
public void onClick(View v) {
if (v.getId() == buttonId) {
Intent leakIntent = new Intent(this, OOMActivity.class);
startActivity(leakIntent);
}
}
}
OOMActivity:
public class OOMActivity extends Activity implements OnClickListener {
private static final int WASTE_SIZE = 20000000;
private byte[] waste;
private int buttonId;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Button BackButton = new Button(this);
BackButton.setText("Back");
buttonId = BackButton.getId();
setContentView(BackButton);
BackButton.setOnClickListener(this);
waste = new byte[WASTE_SIZE];
}
public void onClick(View view) {
if (view.getId() == buttonId) {
finish();
}
}
}
The usual fix is to reboot. Freeing up the operating system for reuse of the space that has been taken over by memory leaks is called garbage collection. In the past, programs have had to explicitly request storage and then return it to the system when it was no longer needed.
Memory leaks are a common error in programming, especially when using languages that have no built in automatic garbage collection, such as C and C++. Typically, a memory leak occurs because dynamically allocated memory has become unreachable.
In java, garbage means unreferenced objects. Garbage Collection is process of reclaiming the runtime unused memory automatically. In other words, it is a way to destroy the unused objects. To do so, we were using free() function in C language and delete() in C++.
Activity destruction does not imply class destruction, just because you don't see the class doesn't mean the OS (Android in this case) lost all references to it and end it. That is why even in the documentations they specify to clean out any handles and objects you no longer need to prevent memory leaks. Cheers.
A few things:
1) You can't judge whether or not your Activity is being leaked just by watching the GC logs; each JVM implementation is free to choose when to garbage-collect objects, even if nothing is referencing them. Note that it IS required to garbage-collect before it throws an OOM error... but if it has enough memory available, it may choose to keep 10 of your activities in memory and then collect them all at once.
2) There may be internal Android structures that keep a reference to your Activity for longer than the lifecycle of the Activity... and the developer has no control over that. For that reason, it's recommended that the Activity not reference any large amounts of data (or if it does, it should explicitly release those references in onDestroy (or even in onPause if you want to be more aggressive).
3) Some JVMs optimize at runtime, such that a chunk of memory that is never written to or accessed is never actually allocated in physical memory. Maybe for that reason, your test is not valid on newer versions of Android. To get around this, you can add a loop that sets some values in the array to random values, and then another loop somewhere else in the code that reads them; this way the JVM is forced to allocate the memory.
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