1

I have a loop that can loop dozens of times for a debt payoff calculator.

One of the many things this loop does is call a method called getSpecialPayment

  private double getSpecialPayment(long debt_id, int month) {

    int thisMonth = Calendar.getInstance().get(Calendar.MONTH) + 1;

    Calendar cal = Calendar.getInstance();
    cal.set(Calendar.MONTH, month + thisMonth);

    SimpleDateFormat df = new SimpleDateFormat("MMM yyyy", Locale.ENGLISH);
    String sMonth = df.format(cal.getTime());

    // Line 884 that fails below 
    String check_for_special = "SELECT payment FROM special_payments WHERE id = " + debt_id + " and month = '" + sMonth + "' LIMIT 1;"; 

    if (!database.isOpen()) {
        open();
    }

    Cursor c = database.rawQuery(check_for_special, null);
    if (c.getCount() == 0) {
        c.close();
        return 0;
    } else {
        c.moveToFirst();
        double amount = c.getDouble(c.getColumnIndex("payment"));
        c.close();
        return amount;
    }
}

I am getting this OutOfMemory stacktrace on the line that build the String (the query) check_for_special.

java.lang.RuntimeException: An error occured while executing doInBackground()
at android.os.AsyncTask$3.done(AsyncTask.java:300)
at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:355)
at java.util.concurrent.FutureTask.setException(FutureTask.java:222)
at java.util.concurrent.FutureTask.run(FutureTask.java:242)
at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:231)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
at java.lang.Thread.run(Thread.java:841)
Caused by: java.lang.OutOfMemoryError
at java.lang.AbstractStringBuilder.enlargeBuffer(AbstractStringBuilder.java:94)
at java.lang.AbstractStringBuilder.append0(AbstractStringBuilder.java:145)
at java.lang.StringBuilder.append(StringBuilder.java:216)
at com.---.---.DebtDataSource.getSpecialPayment(DebtDataSource.java:884)
at com.---.---.DebtDataSource.payoffDebt(DebtDataSource.java:469)
at com.---.---.PlannerFragment$PlannerTask.doInBackground(PlannerFragment.java:156)
at com.---.---.PlannerFragment$PlannerTask.doInBackground(PlannerFragment.java:122)
at android.os.AsyncTask$2.call(AsyncTask.java:288)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
... 4 more

I don't understand why a String initialization would cause the failure; or is this a coincidence where a bigger problem has simply failed at this point?

EDIT:

This picture is of my Heap Dump:

enter image description here

TheLettuceMaster
  • 15,594
  • 48
  • 153
  • 259

1 Answers1

2

You have a larger problem on your hand here. The OOM error caused by getSpecialPayment is symptomatic of memory leaking elsewhere.

Some context, this line here

String check_for_special = "SELECT payment FROM special_payments WHERE id = " + debt_id + " and month = '" + sMonth + "' LIMIT 1;";

really will do something like

StringBuilder builder = new StringBuilder("SELECT payment FROM special_payments WHERE id = ");
builder.append(debt_id);
builder.append("and month = '");
builder.append(sMonth);
builder.append(' LIMIT 1;");

That's all compiled away so you don't see it without stepping through the code.

What this is telling you, is that you are using up so much memory which is not being reclaimed that when you try to do something simple and low memory like this, it is running out of memory.

I suggest creating a heap dump and try to find the greatest offenders of memory.

John Vint
  • 39,695
  • 7
  • 78
  • 108
  • 2
    Agreed, additionally look at any objects created within this function (that's called "dozens of times") with a close eye. For example, CalendarInstance and SimpleDateFormat can be created once and re-used. Also, consider a parameterized query instead of a rawQuery from a String (e.g. http://stackoverflow.com/questions/9857073/queries-with-prepared-statements-in-android), a single query string where only the parameters vary – CSmith Dec 08 '14 at 20:27
  • Ok thanks. Guess I will have to hunt this down and learn how to use the Memory Monitor tool. – TheLettuceMaster Dec 08 '14 at 20:34
  • @KickingLettuce I highly recommend Eclipse MAT. It's really easy to use. jmap is what I use for heap dumps. – John Vint Dec 08 '14 at 20:58
  • @JohnVint I have android studio installed, not Eclipse. Isn't there a MAT plugin you can download only for DDMS? – TheLettuceMaster Dec 08 '14 at 21:28
  • @KickingLettuce Are you able to get a heap dump? Whether through jmap or something else? If that's the case, you can run Eclipse MAT as a standalone app and it takes a Java binary heap dump as an argument. So there doesn't need to be an association with android. – John Vint Dec 08 '14 at 22:07
  • @JohnVint I am, in fact I just attached a picture of it to the original question. Side note: I am also wondering if I am opening up my Database Object in the above method but it is not close if that can be a cause. – TheLettuceMaster Dec 08 '14 at 23:58