Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do threads leak on Android?

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.

MainActivity.java

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();
    }
}

Controller.java

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;
        }
    }
}
like image 746
Cameron Lowell Palmer Avatar asked Oct 18 '12 14:10

Cameron Lowell Palmer


2 Answers

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.

like image 115
acj Avatar answered Sep 21 '22 02:09

acj


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?

like image 21
Cameron Lowell Palmer Avatar answered Sep 19 '22 02:09

Cameron Lowell Palmer