I've been noticing in our Android app that every time we exit to the home screen we increase the heap size (leak) by the amount of the ByteArrayOutputStream. The best I have been able to manage is by adding
this.mByteArrayOutputStream = null;
at the end of run() to prevent the heap size increasing constantly. If anyone could enlighten me I would be very appreciative. I wrote out the following example that illustrates the problem.
public class MainActivity extends Activity {
private Controller mController;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
@Override
protected void onStart() {
super.onStart();
this.mController = new Controller();
mController.connect();
}
@Override
protected void onStop() {
super.onStop();
mController.quit();
}
}
public class Controller {
public volatile ReaderThread mThread;
public Controller() {
super();
}
public void connect() {
mThread = new ReaderThread("ReaderThread");
mThread.start();
}
public void quit() {
mThread.quit();
}
public static class ReaderThread extends Thread {
private volatile boolean isProcessing;
private ByteArrayOutputStream mByteArrayOutputStream;
public ReaderThread(String threadName) {
super(threadName);
}
@Override
public void run() {
this.isProcessing = true;
Log.d(getClass().getCanonicalName(), "START");
this.mByteArrayOutputStream = new ByteArrayOutputStream(2048000);
int i = 0;
while (isProcessing) {
Log.d(getClass().getCanonicalName(), "Iteration: " + i++);
mByteArrayOutputStream.write(1);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
}
try {
mByteArrayOutputStream.reset();
mByteArrayOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
Log.d(getClass().getCanonicalName(), "STOP");
}
public void quit() {
this.isProcessing = false;
}
}
}
Threads are immune to GC because they're garbage collection roots. So, it's likely that the JVM is keeping your ReaderThread
in memory, along with its allocations for member variables, thus creating the leak.
Nulling out the ByteArrayOutputStream
, as you've noted, would make its buffered data (but not the ReaderThread
itself) available for GC.
EDIT:
After some sleuthing, we learned that the Android debugger was causing the perceived leak:
The VM guarantees that any object the debugger is aware of is not garbage collected until after the debugger disconnects. This can result in a buildup of objects over time while the debugger is connected. For example, if the debugger sees a running thread, the associated Thread object is not garbage collected even after the thread terminates.
From the Android Debugging page:
The debugger and garbage collector are currently loosely integrated. The VM guarantees that any object the debugger is aware of is not garbage collected until after the debugger disconnects. This can result in a buildup of objects over time while the debugger is connected. For example, if the debugger sees a running thread, the associated Thread object is not garbage collected even after the thread terminates.
That diminishes the value of DDMS heap monitoring don't you think?
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