0

I have an android app with a non-UI thread that needs to be responsive, but which needs to occasionally perform a long running task. To do this, I make it spawn a thread to run the task. This thread takes a reference to an object. This object appears to leak. I run out of memory amazingly fast.

I anticipate lots of people telling me to use AsyncTask. I find AsyncTask irritating and harder to use than a plain ol' thread, and I am not in the UI thread here. I will listen to advice, but I'd appreciate it if the advice explained why!

I have written a very simple program to demonstrate the problem, which runs a thread that, every second, updates a textview with the value of an integer. Having done so, it spawns a thread which increments the integer by one. The thread also, for no good reason, takes a reference to an object called "BigFatBlob". It leaks like a sieve. I don't understand why. After the program code I have attached a screen shot of part of the MAT analysis of the heap showing the leak and the incoming references.

The function to look at below is "increment()". If anyone can tell me why my program is leaking I'd be very grateful - I'm a bit lost here.

public class MainActivity extends Activity {

  TextView text_;
  int value_;
  boolean ready_;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    text_ = (TextView)findViewById(R.id.textView);
    value_ = 0;
    ready_ = false;
    watcher();
    increment(new BigFatBlob());
  }

  @Override
  public boolean onCreateOptionsMenu(Menu menu) {
    // Inflate the menu; this adds items to the action bar if it is present.
    getMenuInflater().inflate(R.menu.main, menu);
    return true;
  }

  /** Updates the text view object to show the current value */
  public void updateText() {
    Runnable r = new Runnable() {
      @Override
      public void run() {
        text_.setText("" + value_);
      }
    };
    runOnUiThread(r);
  }

  /** runs forever, checking for new values every second */
  private void watcher() {
    Runnable r = new Runnable() {
      @Override
      public void run() {
        while (true) {
          try {
            Thread.sleep(1000);
          } catch (InterruptedException e) {
          }
          if (pollReady()) {
            updateText();
            increment(new BigFatBlob());
          }
        }
      }
    };
    Thread t = new Thread(r);
    t.start();
  }

  /** runs once - increments the value by 1 and flags "ready" */
  public void increment(final BigFatBlob junk) {
    Runnable r = new Runnable() {
      private BigFatBlob j;

      @Override
      public void run() {
        setJ(junk);
        ++value_;
        setReady(true);
      }

      public BigFatBlob getJ() {
        return j;
      }

      public void setJ(BigFatBlob j) {
        this.j = j;
      }
    };
    Thread t = new Thread(r);
    t.start();
  }

  /** ready is synchronized */
  private synchronized void setReady(boolean state) {
    ready_ = state;
  }

  /** get the ready value and, if it was true, make it false again */
  private synchronized boolean pollReady() {
    if (ready_) {
      ready_ = false;
      return true;
    }
    return false;
  }
}

public class BigFatBlob {

  byte[] blob_;

  public BigFatBlob() {
    blob_ = new byte[1000];
  }
}

image of memory leak trace

Andy Newman
  • 1,178
  • 2
  • 8
  • 23
  • Asych is used to push information to a UI element... that would be why I'd use it, also thread needs a handler to push to the main thread and it can get messy. http://stackoverflow.com/questions/6964011/handler-vs-asynctask-vs-thread is a handy starting point I guess. From my LIMITED understanding ASYNCH is made to do specifically what you want here and forcing a thread on it isn't the best approach. I can't see any leaking on my device here so I can't advise on that. – RossC Nov 19 '13 at 13:02
  • Can you create a reproduce able test without having android as a dependency? – John Vint Nov 19 '13 at 13:15
  • RossC - since I'm not on, or talking to, the UI thread, Async seems awkward? Also, I don't need a handler so long as I know what I'm doing with synchronize, in theory. – Andy Newman Nov 19 '13 at 14:26
  • John Vint -that's an interesting idea. I'm guessing "probably". – Andy Newman Nov 19 '13 at 14:26
  • I have recreated the test as a pure java app on windows and it would appear that it does *not* leak. I'm not sure what to make of that. – Andy Newman Nov 19 '13 at 15:54
  • Andy Newman can you knock down a few more of the byte[1000]. Want to see if it's the same thread or different thread (non Thread-324) – John Vint Nov 19 '13 at 16:45

1 Answers1

0

I'm sorry I asked. If I can answer it myself in less than 24 hours from asking I wasn't trying hard enough :(

While I am debugging my test app (in Eclipse) it leaks and leaks and leaks and eventually crashes. If I disconnect the debugger before the crash all the leaked memory suddenly becomes available again and it stops leaking.

I am not 100% sure that a memory analyzer that works over a connection that itself causes my data to leak all over my heap is all that useful.

In fact, it may be the opposite of useful. If this question saves someone else a couple of hours of banging their head on the same artificial wall maybe it was worth asking anyway? Anyone want to shed any more light on this before I close it?

Andy Newman
  • 1,178
  • 2
  • 8
  • 23
  • No breakpoints - just checked. Also, each instance is owned by a thread with a different address. Feels like eclipse prevents dead threads being collected when the debugger is attached? – Andy Newman Nov 19 '13 at 21:22
  • There are a few ways that thread can survive. The two more likely ones is 1. The thread doesn't exit the run method, which looks like it does. 2. Another living thread is pointing to that thread. In this case maybe the debugger is holding the thread. I would find the GC root of your thread in the increment method (if you still care :) ) – John Vint Nov 20 '13 at 13:25
  • Google actually warns about this in the fine print: "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." See the bottom of this page for reference: http://developer.android.com/tools/debugging/index.html – alpartis Jul 07 '14 at 05:04