Hi guys, I've read several posts from developers having OutOfMemory issues, and a lot of them seem to be related to lists. I'm having a similar issue, and the root of the problem seems to be a memory leak in the inflation process.
The problem is particularly tricky to track down, because as of yet it seems impossible to get a view of part of the heap (where the bitmaps reside, and possibly other objects), as also mentioned by Ward and others. I created a small demo, so hopefully some Google engineers (Romain, Dianne) can look at this, and either point out to me what I'm doing wrong, or fix a possible memory leak in the inflation process in the Android framework. I'm sure a lot of people would be very thankful if this issue got resolved, as it often results in sudden crashes of the app, without a clear reason. I must say it's disappointing to read the standard: "use less memory" reply from Google to a lot of these posts, as it seems to be worthy of further investigation. Here's the demo, and the resulting logcat log with some peculiarities contained within. http://www.coffeebreakmedia.com/android_heap_issue.zip A small explanation: it's a very simple list implementation with a ListAdapter. It works fine if the list rows are inflated just once (by re-using the convertView), but I purposely commented out two lines of code: // if(convertView == null) { convertView = inflater.inflate(R.layout.list_row, parent, false); // } This is to ensure that for each row, the XML is inflated again. In this demo, this would be completely unnecessary and a big waste, but in a real life a similar scenario happens when you have a list with a few different types of rows (e.g., a different xml is used for the header of a set of items, which use their own xml). In that case, sometimes the convertView will be of the wrong type, in which case you have to inflate another one. Obviously, this would then happen less often than in this demo, but this only postpones the issue: eventually you'll run into the same problem as is demonstrated here. To test the demo, simply open the project in Eclipse (you can use the emulator, but it also happens on the G1), and follow these instructions: 1. Completely scroll down the list as fast as you can (all the way to Item 199), and then back up again. 2. Keep doing this until you scrolled down & up about 5 times, while watching the LogCat. Eventually, you'll start getting errors such as these: 04-08 12:15:03.380: ERROR/dalvikvm-heap(345): 13824-byte external allocation too large for this process. 04-08 12:15:03.380: ERROR/(345): VM won't let us allocate 13824 bytes Keep scrolling! And you'll see this after a few more ups & downs: 04-08 12:16:48.589: DEBUG/dalvikvm(345): GC freed 0 objects / 0 bytes in 169ms 04-08 12:16:48.589: INFO/dalvikvm-heap(345): Clamp target GC heap from 16.003MB to 16.000MB 04-08 12:16:48.589: INFO/dalvikvm-heap(345): Grow heap (frag case) to 16.000MB for 24-byte allocation 04-08 12:16:48.810: INFO/dalvikvm-heap(345): Clamp target GC heap from 18.003MB to 16.000MB Apparently, the 16MB heap is used completely at this point, which seems rather wasteful considering the simplicity of the application in question. After still more scrolling, you'll get errors such as this: 04-08 12:17:01.700: ERROR/dalvikvm-heap(345): Out of memory on a 708- byte allocation. 04-08 12:17:01.700: INFO/dalvikvm(345): "main" prio=5 tid=3 RUNNABLE 04-08 12:17:01.700: INFO/dalvikvm(345): | group="main" sCount=0 dsCount=0 s=0 obj=0x400103e8 04-08 12:17:01.700: INFO/dalvikvm(345): | sysTid=345 nice=0 sched=0/0 handle=-1093522276 04-08 12:17:01.700: INFO/dalvikvm(345): at java.lang.AbstractStringBuilder.enlargeBuffer (AbstractStringBuilder.java:~100) 04-08 12:17:01.700: INFO/dalvikvm(345): at java.lang.AbstractStringBuilder.append0(AbstractStringBuilder.java: 140) 04-08 12:17:01.700: INFO/dalvikvm(345): at java.lang.StringBuffer.append(StringBuffer.java:257) 04-08 12:17:01.700: INFO/dalvikvm(345): at java.io.StringWriter.write (StringWriter.java:128) 04-08 12:17:01.700: INFO/dalvikvm(345): at java.io.PrintWriter.doWrite(PrintWriter.java:659) 04-08 12:17:01.700: INFO/dalvikvm(345): at java.io.PrintWriter.write (PrintWriter.java:640) 04-08 12:17:01.700: INFO/dalvikvm(345): at java.io.PrintWriter.write (PrintWriter.java:624) 04-08 12:17:01.700: INFO/dalvikvm(345): at java.io.PrintWriter.write (PrintWriter.java:677) 04-08 12:17:01.700: INFO/dalvikvm(345): at java.io.PrintWriter.print (PrintWriter.java:471) 04-08 12:17:01.700: INFO/dalvikvm(345): at java.io.PrintWriter.println(PrintWriter.java:589) 04-08 12:17:01.700: INFO/dalvikvm(345): at java.lang.Throwable.printStackTrace(Throwable.java:267) 04-08 12:17:01.700: INFO/dalvikvm(345): at android.util.Log.getStackTraceString(Log.java:234) 04-08 12:17:01.700: INFO/dalvikvm(345): at com.android.internal.os.RuntimeInit.crash(RuntimeInit.java:278) 04-08 12:17:01.700: INFO/dalvikvm(345): at com.android.internal.os.RuntimeInit$UncaughtHandler.uncaughtException (RuntimeInit.java:75) 04-08 12:17:01.700: INFO/dalvikvm(345): at java.lang.ThreadGroup.uncaughtException(ThreadGroup.java:853) 04-08 12:17:01.700: INFO/dalvikvm(345): at java.lang.ThreadGroup.uncaughtException(ThreadGroup.java:850) 04-08 12:17:01.700: INFO/dalvikvm(345): at dalvik.system.NativeStart.main(Native Method) and this: 04-08 12:18:04.530: ERROR/dalvikvm-heap(345): Out of memory on a 892- byte allocation. 04-08 12:18:04.540: INFO/dalvikvm(345): "main" prio=5 tid=3 RUNNABLE 04-08 12:18:04.540: INFO/dalvikvm(345): | group="main" sCount=0 dsCount=0 s=0 obj=0x400103e8 04-08 12:18:04.540: INFO/dalvikvm(345): | sysTid=345 nice=0 sched=0/0 handle=-1093522276 04-08 12:18:04.540: INFO/dalvikvm(345): at java.lang.AbstractStringBuilder.enlargeBuffer (AbstractStringBuilder.java:~100) 04-08 12:18:04.540: INFO/dalvikvm(345): at java.lang.AbstractStringBuilder.append0(AbstractStringBuilder.java: 140) 04-08 12:18:04.540: INFO/dalvikvm(345): at java.lang.StringBuffer.append(StringBuffer.java:257) 04-08 12:18:04.540: INFO/dalvikvm(345): at java.io.StringWriter.write (StringWriter.java:128) 04-08 12:18:04.540: INFO/dalvikvm(345): at java.io.PrintWriter.doWrite(PrintWriter.java:659) 04-08 12:18:04.540: INFO/dalvikvm(345): at java.io.PrintWriter.write (PrintWriter.java:640) 04-08 12:18:04.540: INFO/dalvikvm(345): at java.io.PrintWriter.write (PrintWriter.java:624) 04-08 12:18:04.540: INFO/dalvikvm(345): at java.io.PrintWriter.write (PrintWriter.java:677) 04-08 12:18:04.540: INFO/dalvikvm(345): at java.io.PrintWriter.print (PrintWriter.java:471) 04-08 12:18:04.540: INFO/dalvikvm(345): at java.io.PrintWriter.println(PrintWriter.java:589) 04-08 12:18:04.540: INFO/dalvikvm(345): at java.lang.Throwable.printStackTrace(Throwable.java:267) 04-08 12:18:04.540: INFO/dalvikvm(345): at android.util.Log.getStackTraceString(Log.java:234) 04-08 12:18:04.540: INFO/dalvikvm(345): at com.android.internal.os.RuntimeInit.crash(RuntimeInit.java:286) 04-08 12:18:04.540: INFO/dalvikvm(345): at com.android.internal.os.RuntimeInit$UncaughtHandler.uncaughtException (RuntimeInit.java:75) 04-08 12:18:04.540: INFO/dalvikvm(345): at java.lang.ThreadGroup.uncaughtException(ThreadGroup.java:853) 04-08 12:18:04.540: INFO/dalvikvm(345): at java.lang.ThreadGroup.uncaughtException(ThreadGroup.java:850) 04-08 12:18:04.540: INFO/dalvikvm(345): at dalvik.system.NativeStart.main(Native Method) Until eventually, the story ends rather sadly: 04-08 12:18:11.882: INFO/WindowManager(47): WIN DEATH: Window{43471f70 com.test/com.test.ListTestActivity} 04-08 12:18:11.882: INFO/ActivityManager(47): Process com.test (pid 345) has died. And the app simply crashes. By the way, the same behavior will occur if you remove the list icon. In a real app scenario, this also seems to result in OutOfMemoryErrors such as these: 03-26 14:24:14.974: ERROR/dalvikvm-heap(381): 64000-byte external allocation too large for this process. 03-26 14:24:14.984: ERROR/(381): VM won't let us allocate 64000 bytes 03-26 14:24:14.994: DEBUG/AndroidRuntime(381): Shutting down VM 03-26 14:24:14.994: WARN/dalvikvm(381): threadid=3: thread exiting with uncaught exception (group=0x40013e28) 03-26 14:24:14.994: ERROR/AndroidRuntime(381): Uncaught handler: thread main exiting due to uncaught exception 03-26 14:24:15.184: ERROR/AndroidRuntime(381): java.lang.OutOfMemoryError: bitmap size exceeds VM budget 03-26 14:24:15.184: ERROR/AndroidRuntime(381): at android.graphics.Bitmap.nativeCreate(Native Method) 03-26 14:24:15.184: ERROR/AndroidRuntime(381): at android.graphics.Bitmap.createBitmap(Bitmap.java:343) 03-26 14:24:15.184: ERROR/AndroidRuntime(381): at android.view.View.buildDrawingCache(View.java:5219) 03-26 14:24:15.184: ERROR/AndroidRuntime(381): at android.view.View.getDrawingCache(View.java:5112) 03-26 14:24:15.184: ERROR/AndroidRuntime(381): at android.view.ViewGroup.drawChild(ViewGroup.java:1355) 03-26 14:24:15.184: ERROR/AndroidRuntime(381): at android.view.ViewGroup.dispatchDraw(ViewGroup.java:1192) 03-26 14:24:15.184: ERROR/AndroidRuntime(381): at android.widget.AbsListView.dispatchDraw(AbsListView.java:1125) 03-26 14:24:15.184: ERROR/AndroidRuntime(381): at android.widget.ListView.dispatchDraw(ListView.java:2778) 03-26 14:24:15.184: ERROR/AndroidRuntime(381): at android.view.View.draw(View.java:5422) 03-26 14:24:15.184: ERROR/AndroidRuntime(381): at android.view.ViewGroup.drawChild(ViewGroup.java:1420) 03-26 14:24:15.184: ERROR/AndroidRuntime(381): at android.view.ViewGroup.dispatchDraw(ViewGroup.java:1192) Please check out the demo, and let us know your findings. One work- around is to put all your different rows into one XML, and then use GONE visibility to switch parts on and off depending on the row. However, this may not fix the issue entirely, as you probably still need to inflate again when you flip the screen and the Activity is recreated. Kind regards, - Niek On Apr 8, 12:39 am, Dianne Hackborn <hack...@android.com> wrote: > On Tue, Apr 7, 2009 at 2:03 PM, Ward Willats <goo...@wardco.com> wrote: > > (I mean, if I make a really small 4 bit indexed color png (no alpha) > > does it get "blown up" to RGB_888 or ARGB in the view buffers before > > being composited and handed to frame buffer memory?) > > They will generally either be loaded to 16bpp if there is no alpha channel, > or 32bpp is there is an alpha channel. > > > And 9 patches...can I get their RAM consumption by taking their final > > dimensions, and? > > Yes, 9-patches are basically just bitmaps. > > > If you rotate the screen, do two copies of the view hierarchy exist > > for a moment? > > There is certainly a chance this can happen, though generally the first > activity is destroyed before the second is created (but any lingering > reference on any of the objects in the first activity can cause it to stay > around until that ref goes away). Those won't duplicate bitmaps, though, > unless you are loading them yourself. > > -- > Dianne Hackborn > Android framework engineer > hack...@android.com > > Note: please don't send private questions to me, as I don't have time to > provide private support, and so won't reply to such e-mails. All such > questions should be posted on public forums, where I and others can see and > answer them. --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Android Developers" group. To post to this group, send email to android-developers@googlegroups.com To unsubscribe from this group, send email to android-developers-unsubscr...@googlegroups.com For more options, visit this group at http://groups.google.com/group/android-developers?hl=en -~----------~----~----~----~------~----~------~--~---