ZhouWeikuan / cocos2d

cocos2d for android, based on cocos2d-android-0.82, and now ported from cocos2d-iphone 0.99.4. The googlecode address is here: http://code.google.com/p/cocos2d-android-1/ . There are several demos to watch.
610 stars 291 forks source link

bitmap size exceeds VM budget #30

Open irwinb opened 13 years ago

irwinb commented 13 years ago

In CCTexture2D.java, the method "public void initWithImage(Bitmap)" does not seem to call recycle() on the provided bitmap under all circumstances. I think the OOM issues I have been running into are a result of this. I temporarily fixed this by calling recycle on the bitmap at the end of the routine.

opengenius commented 13 years ago

Look deeper: init->loadTexture, and you are not the first, who thinks so.

irwinb commented 13 years ago

Ok, thank you for the clarification.

irwinb commented 13 years ago

I took the recycle and "bmp = null" line out and am running into the OOM problem again. What else could be calling it if loadTexture calls recycle on bmp? Is it required to nullify the bitmap for it to properly be recycled?

dustinanon commented 13 years ago

The bmp should be collected along with the parent CCTexture2D. Are you using the CCTextureCache to manage your textures?

As long as you don't hold a reference to the CCTexture2D object, then it should be GC'd along with all of its members.

irwinb commented 13 years ago

Yes, i am using CCTextureCache to manage my textures. Maybe the bitmap doesn't get released soon enough after it is loaded and before the next one begins loading? If I set the bitmap to null in the method, the problem hasn't shown up.

dustinanon commented 13 years ago

Well then I'm a bit stumped from your description alone... Can you post a code snippet so that I can walk through the actual operations you're performing. Maybe there is something that's not apparent at first glance?

dustinanon commented 13 years ago

Actually I just got this same exception when I tried to run the Sprites Test.


E/AndroidRuntime( 3836): FATAL EXCEPTION: GLThread 20
E/AndroidRuntime( 3836): java.lang.OutOfMemoryError: bitmap size exceeds VM budget
E/AndroidRuntime( 3836):    at android.graphics.Bitmap.nativeCreate(Native Method)
E/AndroidRuntime( 3836):    at android.graphics.Bitmap.createBitmap(Bitmap.java:468)
E/AndroidRuntime( 3836):    at org.cocos2d.opengl.CCTexture2D.initWithImage(CCTexture2D.java:255)
E/AndroidRuntime( 3836):    at org.cocos2d.grid.CCGridBase$1.load(CCGridBase.java:134)
E/AndroidRuntime( 3836):    at org.cocos2d.opengl.GLResourceHelper$2.perform(GLResourceHelper.java:94)
E/AndroidRuntime( 3836):    at org.cocos2d.opengl.GLResourceHelper.update(GLResourceHelper.java:127)
E/AndroidRuntime( 3836):    at org.cocos2d.nodes.CCDirector.drawCCScene(CCDirector.java:700)
E/AndroidRuntime( 3836):    at org.cocos2d.nodes.CCDirector.onDrawFrame(CCDirector.java:665)
E/AndroidRuntime( 3836):    at org.cocos2d.opengl.GLSurfaceView$GLThread.guardedRun(GLSurfaceView.java:1245)
E/AndroidRuntime( 3836):    at org.cocos2d.opengl.GLSurfaceView$GLThread.run(GLSurfaceView.java:1056)
irwinb commented 13 years ago

My first guess is that the loadTexture(gl) method in CCTexture2D.init (where the recycling happens) gets queued and may get executed after the next bitmap is loaded into memory.

private static CCTexture2D createTextureFromFilePath(final String path) {

    CCTexture2D tex = new CCTexture2D();
    tex.setLoader(new GLResourceHelper.GLResourceLoader() {

        @Override
        public void load(Resource res) {
            try {
                InputStream is = CCDirector.sharedDirector().getActivity().getAssets().open(path);

                BitmapFactory.Options opts = new BitmapFactory.Options();
                opts.inPreferredConfig = ((CCTexture2D)res).pixelFormat();
                Bitmap bmp = BitmapFactory.decodeStream(is, null, opts);

                is.close();

                ((CCTexture2D)res).initWithImage(bmp);

                //------Additions here---------
                bmp.recycle();
                bmp = null;
                //-----------------------------
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    });

    return tex;
}
opengenius commented 13 years ago

you should load your textures in GL thread (schedule or perform in GLResourceHelper).

dustinanon commented 13 years ago

Does that solve the issue of the resources not being released?

irwinb commented 13 years ago

I always load textures in the GL thread, as per Issue 20.

opengenius commented 13 years ago

may be you can provide some demo for your problem?

dustinanon commented 13 years ago

Well, I can recreate this problem over and over by just switching between the tests. It doesn't always happen at the same point, but usually after I switch between 8 or 9 different tests, I will try to load another one and get the same exception:


E/AndroidRuntime( 2075): FATAL EXCEPTION: GLThread 19
E/AndroidRuntime( 2075): java.lang.OutOfMemoryError: bitmap size exceeds VM budget
E/AndroidRuntime( 2075):    at android.graphics.Bitmap.nativeCreate(Native Method)
E/AndroidRuntime( 2075):    at android.graphics.Bitmap.createBitmap(Bitmap.java:477)
E/AndroidRuntime( 2075):    at org.cocos2d.opengl.CCTexture2D.initWithImage(CCTexture2D.java:264)
E/AndroidRuntime( 2075):    at org.cocos2d.grid.CCGridBase$1.load(CCGridBase.java:134)
E/AndroidRuntime( 2075):    at org.cocos2d.opengl.GLResourceHelper$2.perform(GLResourceHelper.java:94)
E/AndroidRuntime( 2075):    at org.cocos2d.opengl.GLResourceHelper.update(GLResourceHelper.java:128)
E/AndroidRuntime( 2075):    at org.cocos2d.nodes.CCDirector.drawCCScene(CCDirector.java:700)
E/AndroidRuntime( 2075):    at org.cocos2d.nodes.CCDirector.onDrawFrame(CCDirector.java:665)
E/AndroidRuntime( 2075):    at org.cocos2d.opengl.GLSurfaceView$GLThread.guardedRun(GLSurfaceView.java:1245)
E/AndroidRuntime( 2075):    at org.cocos2d.opengl.GLSurfaceView$GLThread.run(GLSurfaceView.java:1056)

I also get a heap dump, if you can make sense of this, here it is too:


I/DEBUG   (   70): *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
I/DEBUG   (   70): Build fingerprint: 'sprint/SPH-P100/SPH-P100:2.3.4/GINGERBREAD/EF17:user/release-keys'
I/DEBUG   (   70): pid: 2075, tid: 2076  >>> org.cocos2d <<<
I/DEBUG   (   70): signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 00000000
I/DEBUG   (   70):  r0 0005e9f0  r1 00000007  r2 442e1140  r3 00000024
I/DEBUG   (   70):  r4 00000000  r5 000f5c70  r6 ad3b8920  r7 406df260
I/DEBUG   (   70):  r8 442e1168  r9 45cd9f8c  10 00000000  fp 801a5374
I/DEBUG   (   70):  ip 00000000  sp 100ffa88  lr ad32c0d4  pc ad32c0e4  cpsr 60000010
I/DEBUG   (   70):  d0  0063006400350032  d1  002e006400320000
I/DEBUG   (   70):  d2  0064006900720036  d3  0047004300430000
I/DEBUG   (   70):  d4  0000000000388540  d5  0444000000008000
I/DEBUG   (   70):  d6  0000000000000000  d7  0000000000000000
I/DEBUG   (   70):  d8  0000000000000000  d9  0000000000000000
I/DEBUG   (   70):  d10 0000000000000000  d11 0000000000000000
I/DEBUG   (   70):  d12 0000000000000000  d13 0000000000000000
I/DEBUG   (   70):  d14 0000000000000000  d15 0000000000000000
I/DEBUG   (   70):  d16 000000004077a180  d17 3ff0000000000000
I/DEBUG   (   70):  d18 42eccefa43de3400  d19 3fbc71c71c71c71c
I/DEBUG   (   70):  d20 4008000000000000  d21 3fd99a27ad32ddf5
I/DEBUG   (   70):  d22 3fd24998d6307188  d23 3fcc7288e957b53b
I/DEBUG   (   70):  d24 3fc74721cad6b0ed  d25 3fc39a09d078c69f
I/DEBUG   (   70):  d26 0000000000000000  d27 0000000000000000
I/DEBUG   (   70):  d28 0000000000000000  d29 0000000000000000
I/DEBUG   (   70):  d30 0000000000000000  d31 0000000000000000
I/DEBUG   (   70):  scr 80000012
I/DEBUG   (   70): 
I/DEBUG   (   70):          #00  pc 0002c0e4  /system/lib/libandroid_runtime.so
I/DEBUG   (   70):          #01  pc 00032150  /system/lib/libandroid_runtime.so
I/DEBUG   (   70):          #02  pc 00017e74  /system/lib/libdvm.so
I/DEBUG   (   70): 
I/DEBUG   (   70): code around pc:
I/DEBUG   (   70): ad32c0c4 e1a0e00f e59cf1a4 e3010f03 ebffe6a1 
I/DEBUG   (   70): ad32c0d4 e5962028 e1a04000 e1a0a000 e59f02c0 
I/DEBUG   (   70): ad32c0e4 e5d43000 e08f6000 e3530000 0a000019 
I/DEBUG   (   70): ad32c0f4 e1a00003 e3a01000 ea000000 e7da0001 
I/DEBUG   (   70): ad32c104 e7d6c001 e2811001 e27ce001 33a0e000 
I/DEBUG   (   70): 
I/DEBUG   (   70): code around lr:
I/DEBUG   (   70): ad32c0b4 e3a03001 e1a00005 e1a01007 e595c000 
I/DEBUG   (   70): ad32c0c4 e1a0e00f e59cf1a4 e3010f03 ebffe6a1 
I/DEBUG   (   70): ad32c0d4 e5962028 e1a04000 e1a0a000 e59f02c0 
I/DEBUG   (   70): ad32c0e4 e5d43000 e08f6000 e3530000 0a000019 
I/DEBUG   (   70): ad32c0f4 e1a00003 e3a01000 ea000000 e7da0001 
I/DEBUG   (   70): 
I/DEBUG   (   70): stack:
I/DEBUG   (   70):     100ffa48  40791d70  
I/DEBUG   (   70):     100ffa4c  45cd9f48  
I/DEBUG   (   70):     100ffa50  19475fdc  
I/DEBUG   (   70):     100ffa54  00000001  
I/DEBUG   (   70):     100ffa58  003686b8  
I/DEBUG   (   70):     100ffa5c  0000a000  
I/DEBUG   (   70):     100ffa60  406df260  
I/DEBUG   (   70):     100ffa64  442e112c  
I/DEBUG   (   70):     100ffa68  0011eb90  
I/DEBUG   (   70):     100ffa6c  8014920f  /system/lib/libdvm.so
I/DEBUG   (   70):     100ffa70  ad3b8920  
I/DEBUG   (   70):     100ffa74  000f5c70  
I/DEBUG   (   70):     100ffa78  ad3b8920  
I/DEBUG   (   70):     100ffa7c  406df260  
I/DEBUG   (   70):     100ffa80  df002777  
I/DEBUG   (   70):     100ffa84  e3a070ad  
I/DEBUG   (   70): #00 100ffa88  ad3b8920  
I/DEBUG   (   70):     100ffa8c  00000001  
I/DEBUG   (   70):     100ffa90  000f5c70  
I/DEBUG   (   70):     100ffa94  406d2578  
I/DEBUG   (   70):     100ffa98  00000000  
I/DEBUG   (   70):     100ffa9c  45cd9f8c  
I/DEBUG   (   70):     100ffaa0  000f5c70  
I/DEBUG   (   70):     100ffaa4  ad332154  /system/lib/libandroid_runtime.so
I/DEBUG   (   70): #01 100ffaa8  100ffae8  
I/DEBUG   (   70):     100ffaac  00000000  
I/DEBUG   (   70):     100ffab0  100ffb70  
I/DEBUG   (   70):     100ffab4  45cd9f98  
I/DEBUG   (   70):     100ffab8  100ffac4  
I/DEBUG   (   70):     100ffabc  80117e78  /system/lib/libdvm.so
dustinanon commented 13 years ago

Here is a stack trace of all the running threads on the org.cocos2d process when it crashes. This was dumped to data/anr/traces.txt

----- pid 3167 at 2011-08-31 20:39:15 -----
Cmd line: org.cocos2d

DALVIK THREADS:
(mutexes: tll=0 tsl=0 tscl=0 ghl=0 hwl=0 hwll=0)
"main" prio=5 tid=1 NATIVE
  | group="main" sCount=1 dsCount=0 obj=0x4001f190 self=0xce38
  | sysTid=3167 nice=0 sched=0/0 cgrp=default handle=-1345006496
  at android.os.MessageQueue.nativePollOnce(Native Method)
  at android.os.MessageQueue.next(MessageQueue.java:119)
  at android.os.Looper.loop(Looper.java:117)
  at android.app.ActivityThread.main(ActivityThread.java:3687)
  at java.lang.reflect.Method.invokeNative(Native Method)
  at java.lang.reflect.Method.invoke(Method.java:507)
  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:842)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:600)
  at dalvik.system.NativeStart.main(Native Method)

"GLThread 20" prio=5 tid=9 NATIVE
  | group="main" sCount=1 dsCount=0 obj=0x406a00d8 self=0x580400
  | sysTid=3190 nice=0 sched=0/0 cgrp=default handle=5356056
  at com.google.android.gles_jni.EGLImpl.eglSwapBuffers(Native Method)
  at org.cocos2d.opengl.GLSurfaceView$EglHelper.swap(GLSurfaceView.java:978)
  at org.cocos2d.opengl.GLSurfaceView$GLThread.guardedRun(GLSurfaceView.java:1252)
  at org.cocos2d.opengl.GLSurfaceView$GLThread.run(GLSurfaceView.java:1056)

"Binder Thread #3" prio=5 tid=10 NATIVE
  | group="main" sCount=1 dsCount=0 obj=0x40776430 self=0x352e50
  | sysTid=3181 nice=0 sched=0/0 cgrp=default handle=5956024
  at dalvik.system.NativeStart.run(Native Method)

"Binder Thread #2" prio=5 tid=8 NATIVE
  | group="main" sCount=1 dsCount=0 obj=0x40522938 self=0x2717b0
  | sysTid=3174 nice=0 sched=0/0 cgrp=default handle=2004080
  at dalvik.system.NativeStart.run(Native Method)

"Binder Thread #1" prio=5 tid=7 NATIVE
  | group="main" sCount=1 dsCount=0 obj=0x40511980 self=0x270dd8
  | sysTid=3173 nice=0 sched=0/0 cgrp=default handle=2003608
  at dalvik.system.NativeStart.run(Native Method)

"Compiler" daemon prio=5 tid=6 VMWAIT
  | group="system" sCount=1 dsCount=0 obj=0x4050d828 self=0x2d2cb0
  | sysTid=3172 nice=0 sched=0/0 cgrp=default handle=1513056
  at dalvik.system.NativeStart.run(Native Method)

"JDWP" daemon prio=5 tid=5 VMWAIT
  | group="system" sCount=1 dsCount=0 obj=0x4050d778 self=0x2d2af0
  | sysTid=3171 nice=0 sched=0/0 cgrp=default handle=1174928
  at dalvik.system.NativeStart.run(Native Method)

"Signal Catcher" daemon prio=5 tid=4 RUNNABLE
  | group="system" sCount=0 dsCount=0 obj=0x4050d6b8 self=0x171528
  | sysTid=3170 nice=0 sched=0/0 cgrp=default handle=1174864
  at dalvik.system.NativeStart.run(Native Method)

"GC" daemon prio=5 tid=3 VMWAIT
  | group="system" sCount=1 dsCount=0 obj=0x4050d610 self=0x1713f0
  | sysTid=3169 nice=0 sched=0/0 cgrp=default handle=2002200
  at dalvik.system.NativeStart.run(Native Method)

"HeapWorker" daemon prio=5 tid=2 NATIVE
  | group="system" sCount=1 dsCount=0 obj=0x4050d558 self=0x11eb90
  | sysTid=3168 nice=0 sched=0/0 cgrp=default handle=2673264
  at com.google.android.gles_jni.GLImpl.glDeleteFramebuffersOES(Native Method)
  at org.cocos2d.grid.CCGrabber.finalize(CCGrabber.java:99)
  at dalvik.system.NativeStart.run(Native Method)

----- end 3167 -----
dustinanon commented 13 years ago

Okay, that last bit is nonsense it seems, so ignore that. I installed the Eclipse Memory Analyzer and keep switching the tests until the application crashed. Then, before clicking close I went to DDMS and click the Dump HPROF button.

The Eclipse Memory Analyzer reported that at the time the WeakHashMap reloadMap inside of GLResourceHelper was holding on to all of its keys and values, none of them were being released. Every single that had called GLResourceHelper.addLoader was being held inside of reloadMap (both res and loader).

So overtime this was adding up and causing the leak.

I don't know why those objects weren't being collected since WeakHashMap is designed explicitly to be garbage collected.

tnousis commented 12 years ago

hi , is there a fix for this particular issue ? im getting the same behavior , loading textures using CCtexturecache one after the other raises the OOM exception as it seems that the bitmaps aren't released at the time of the next allocation , but having a timer and allocating the textures with an interval of 100ms for example then im able to load as many textures as i want

hqq133 commented 11 years ago

@dustinanon ,tnousis have you fixed the issue? how to fix it