11

I am experiencing a very strange phenomenon (test device: HTC Desire HD, Android 2.3.5). I know that System.gc() is needless and discouraged, and I don't try to suggest otherwise, but the point is that it shouldn't cause issues either (i.e. it should be useless at most).

I have an application which contains a GLSurfaceView in its view hierarchy. The GLSurfaceView is instantiated and added in the Activity.onCreate(). Normally, the application works like this:

  1. User starts the app and goes to mainmenu
  2. User chooses a mainmenu item which sets the GLSurfaceView to View.VISIBLE
  3. User plays with the in-built game on GLSurfaceView
  4. User goes to mainmenu and exits the activity (=> Activity.finish() is called)

My Activity.onPause() looks like this:

mGameThread.pause(); // gameThread is my custom thread class for the in-built game
mGLView.onPause(); // pause the renderer thread

So far so good, everything works fine. However, issues appear after I add the following code to onPause() (for the case when the user exits the game from the mainmenu):

mGameThread.pause(); // gameThread is my custom thread class for the in-built game
mGLView.onPause(); // pause the renderer thread    
if (isFinishing()) {
    System.gc();
}

In details: if the Activity is started for the first time (= i.e. the app process didn't exist before), everything works fine. However, starting from the 2nd start of the activity (= after the first exit from the mainmenu, i.e. after the first Activity.finish()), the framerate of GLSurfaceView is reduced by 40-50%, the in-built game becomes slow.

If I remove the System.gc() call, the problem disappears. Moreover, if I do the following, it also gets rid of the problem:

mGameThread.pause(); // gameThread is my custom thread class for the in-built game
mGLView.onPause(); // pause the renderer thread
if (isFinishing()) {
    // 1. get layout root of View hierarchy

    // 2. recursively remove (detach) all Views

    // 3. call GC
    System.gc();
}

I didn't add concrete code because it's complex, so I used comments. If I just detach the GLSurfaceView via removeView(), it is not enough. The entire view hierarchy needs to be cleared.

Note that I couldn't find any memory leaks (no Activity leak via drawables/statics etc.). Moreover, of course, the gameThread properly exits when the app is closed (I just didn't include its source code).

Any ideas, guesses? Apparently, System.gc() seems to cause some issues for the Activity/layout destroying mechanism of Android. Again, as I said, if I remove System.gc(), the problem disappears.

Thomas Calc
  • 2,994
  • 3
  • 30
  • 56
  • Just to make it more complicated: if I surround the body of my onPause() between **Debug.startMethodTracing()** and **Debug.stopMethodTracing**", the problem disappears! I.e. when I tried profiling in order to find the problem, the problem disappears. – Thomas Calc Jul 22 '12 at 17:47
  • Have you tried removing the -if isFinished statement- and moving it to the onDestroy? – DroidBender Sep 18 '12 at 07:21
  • 1
    maybe this well help http://stackoverflow.com/questions/2414105/why-is-it-a-bad-practice-to-call-system-gc – Mohammad Ersan Sep 18 '12 at 08:34
  • @Martijn Van Mierloo: yes, but it didn't help. I tried all such positioning stuff. – Thomas Calc Sep 20 '12 at 23:53
  • I read somewhere that onDestroy() and onPause() is not the ideal place for cleanup because onDestroy() is guranteed to get called everytime you finish you activity, it may get called the next time you start your activity so first if will call is onDestroy then onCreate. I happened with me in Apache Attack. Well the best place for clean up work is onStop() so try putting it there... – AZ_ Sep 28 '12 at 17:06
  • In my game Ninja Defender ( a physics based game ) I have cleaned up objects (destroying physics world) everything in onStop as I remember onPause gives me obnoxious results with physics objects. – AZ_ Sep 28 '12 at 17:07

1 Answers1

7

I have experience of Android Game Programming. I used to clear all the view in hierarchy because when running threads if you call System.gc() sometimes it happens that your thread has a reference to some of your view, even if you call system.gc() this view won't get removed and if you keep playing again and again this game you will notice that your heap memory is started growing.

It depends upon the memory leak, if you are leaking some KB memory it will take more time to crash your game. The best way it to use Eclipse Memory Anlyser (Eclipse MAT) and compare your stacks.

Step1: take memory snap shot when you start your game for first time Step2: take memory snap shot when you start your game second time Step3: Now compare your both stacks of snapshots it will tell you the difference.

It is a very useful tool. I was having huge memory issues in my game Apache Attack. I fixed them using this awesome tool. Follow this ECLIPSE MAT TUTORIAL

AZ_
  • 21,688
  • 25
  • 143
  • 191
  • If you game is lagging 2end time you play it. There is surely memory allocation/deallocation issue. Also look for fonts if you are loading your custom ones. – AZ_ Sep 18 '12 at 07:19
  • 1
    really good talk on memory management for android: http://www.youtube.com/watch?v=_CruQY55HOk – chris-tulip Sep 18 '12 at 21:05
  • I used Eclipse MAT before, but no sign of any leak. Your answer is not entirely clear to me (the initial, non-empirical part): in my case, System.gc() *does* cause a problem, and when omitting System.gc(), everything works correctly. If anything was leaking a View, the reduced performance would be present independently of the gc(). – Thomas Calc Sep 20 '12 at 03:37
  • 1
    If you are nullifying resource then you should call System.gc(). If you call System.gc() it MAY happen that some resource is in between freeing up its resources but as you called System.gc() its finalize() method have get called and due to this it won't get collected by GC because finalize() is called when object is being collected by GC and if it gets called already it won't be garbage collected. I am not a native English speaker I tried to make you understand in best way I can. – AZ_ Sep 21 '12 at 11:38
  • If you compare stacks through eclipse MAT it will make you 100% clear by pointing out which resources are increasing and not collected by GC. Please read about surfaceview at android developer site. You will understand what I want to make you understand. – AZ_ Sep 21 '12 at 11:39
  • 1 golden rule I learned in my game programming experience. 1 system.gc() for 1 null e.g mListMissiles.clear(); mListMissiles = null; System.gc() no null, no call to GC it will misbehave 2 null call 2 times GC for each. I hope you understood. – AZ_ Sep 21 '12 at 11:41
  • I'm a bit confused, so I have two questions: 1) In your first comment, you state that if finalize() was already called, then later the object cannot be collected because finalize() may only be called once. This is not clear to me -- as far as I know, finalize() is called only once, but it doesn't mean GC will be prevented. The only case when GC is prevented is if we still have a managed reference somewhere (such a reference can be created within finalize() too, but that's irrelevant now). – Thomas Calc Sep 22 '12 at 19:36
  • 2) About your golden rule: I understand your text completely, but technically, I don't see why it would be true. mListMissiles points to a list on the Dalvik heap. Once you don't have references to this list (object), it is ready for garbage collection. Your explicit gc() call gives a hint to Dalvik that it should be collected. I don't see any evidence why the number of GC calls would influence correctness. And what does "misbehave" mean? Will you get a memory leak? It sounds impossible in the mListMissiles example, if there is no other error in the software. – Thomas Calc Sep 22 '12 at 19:41
  • 3) Regarding the SurfaceView page, I don't see any relevant parts there (about GC, finalizers, or else). Could you provide me a link (with a HTML anchor or quoted text or informal description about where I can find the relevant paragraphs; so anything more specific). – Thomas Calc Sep 22 '12 at 19:42
  • mListMissiles is just an example. I was talking about textures, bitmaps and fonts. If you also decompile glue or other famous brands games (just for learning) you will notice that after unloading gl texture they are nullifying and calling System.gc() for each null. This is a common norm; I don't know the logic but this works. – AZ_ Sep 23 '12 at 08:44
  • 1
    Thanks, this is more clear, but what exactly do they (typically) nullfy? GL textures are stored natively, and can be deleted via the appropriate GL call. So what do they null? The tex coord buffer and such stuff are all only primitive (int) Java arrays. – Thomas Calc Sep 23 '12 at 17:53
  • AndEngine specific example: 1) detach child (2) unload texture from hardware (3) remove texture from managed buffer texture + image data (4) null your sprite (texture) and call to System.gc(). its really difficult to get point without observing code. Please see code of Fruit Ninja Android version. – AZ_ Sep 24 '12 at 12:26
  • If you remove root of graph and it is not being referenced by any other object then it is collectable by GC when you do root = null but if you are between of releasing resources and you called system.gc as it may not be thread safe it became the reason of getting called finalize()...and this object is being referenced by some other object, so it won't get remove by your gc. You should take very care while handling with memory. If you know 100% then do this otherwise there are 100% chance that you will mess up. – AZ_ Sep 24 '12 at 12:30
  • Thanks. Questions/requests: 1) the Fruit Ninja (or its free version) source code is not available anywhere. Can you send it to me? – Thomas Calc Sep 24 '12 at 18:20
  • 2) I understand that GC cannot yet collect the mentioned object (if it's called in the middle of releasing), but why cannot do it LATER? When all resources were freed, there is nothing that should keep the reference to the object anymore. So I still can't see what **truly** causes the memory LEAK. – Thomas Calc Sep 24 '12 at 18:21
  • 1
    EDIT: if I get you correctly, the following happens: 1. the root layout is set to null; 2. GC is called, finalize() is called, but we're in the middle of releasing resources so the layout doesn't get collected; 3. When GC tries to release it again, finalize() will not be called (as it's callable only **once**), so only the managed side will be released, and nothing will release the native side (= native Android surface) – Thomas Calc Sep 24 '12 at 18:24
  • My problem with this is the following: "but as you called System.gc() its finalize() method have get called". Because finalize() is only called if GC initially finds **no references** to that object before finalize() is called. The scenario what you mention is possible only if a new reference is created **during** the finalize(), and I don't see why the native releasing process would create an additional reference to the object. AND if there was already a reference, then GC (and thus the first finalize()) couldn't have been called in the first place. Contradiction to me. – Thomas Calc Sep 24 '12 at 18:32
  • on which android os version you are running your game? try it on different versions and see the effect as GC implementation is different; currently we have concurrent GC. [I don't understand what you said in your last comment please use simple english] Only eclipse MAT can help you. Please read those pages below you will get to know how to extract this information with eclipse mat (1) http://android-developers.blogspot.com/2011/03/memory-analysis-for-android.html (2)http://live.eclipse.org/node/520 – AZ_ Sep 25 '12 at 07:34
  • ok I get if after reading many times :) Well you need to see how many threads you are creating ok. Now how to do it ? rite. Listen. First you need to know how to collect data for memory analysis, its very important. If you get the rite data MAT is so intelligent that it will automatically point out suspicious objects in memory. let me search those slides again but please see the above links and the talk ok. I suspect thread is creating a problem with the view as it keeps reference to that view and view is recreating with the old copy present in memory. did you get me brother ? – AZ_ Sep 25 '12 at 07:38
  • More links on tutorials Eclipse MAT (1) http://www.javacodegeeks.com/2011/08/eclipse-memory-analyzer-mat.html (2) http://memoryanalyzer.blogspot.com/ [**very good blog**] (3) http://ttlnews.blogspot.com/2010/01/attacking-memory-problems-on-android.html – AZ_ Sep 25 '12 at 07:40
  • Very important is this mention: "When a Drawable is attached to a view, the view is set as a callback on the drawable.". So here's implicitly a reference back introduced! And you probably have images in your app, so for 99.99% certainty, you could have a memory leak here if you're not "setting the stored drawables' callbacks to null when the activity is destroyed". reference http://www.curious-creature.org/2008/12/18/avoid-memory-leaks-on-android/ – AZ_ Sep 25 '12 at 08:51
  • I was looking for something regarding finalizers and look I have found it http://pastebin.com/FzqwFyLh. Effective Java 2end Edition Page 27 by Jousha Bloch "Avoid Finalizers" – AZ_ Sep 25 '12 at 09:20
  • As I told you before, I already used MAT several times on this app, and it didn't find memory leaks. Of course, I can keep trying, I know. – Thomas Calc Sep 25 '12 at 14:09
  • You write: "[..] and view is recreating with the old copy present in memory.". I think I understand you. But leak is only possible **if the reference is recreated DURING FINALIZING**, right? So our leak appears because finalizer is called ONCE on the same object. And recreating a reference to a garbage object is only possilbe in finalizer! – Thomas Calc Sep 25 '12 at 14:12
  • (About MAT, I didn't mean only automatic leak search. I used to check everything manually as well.) – Thomas Calc Sep 25 '12 at 14:16
  • Did you tested it on different Android OS versions ? – AZ_ Sep 26 '12 at 13:04
  • Only 2.3, I'll test it on Galaxy S3 (i.e. Android 4.0.3) today. – Thomas Calc Sep 27 '12 at 07:38
  • please also see android:launchMode http://developer.android.com/guide/topics/manifest/activity-element.html#lmode – AZ_ Sep 27 '12 at 08:15
  • 1
    Results: bug appears on Desire HD (Android 2.3.5), and does not appear on Samsung Galaxy S3 (Android 4.0.3). About launchMode: I know how it works. But I think that it should not be involved in this, i.e. if a launchMode change would solve the problem, then it would be (at most) a workaround, but the root of the problem lies elsewhere. (Probably related with the finalizer issues you've written about.) – Thomas Calc Sep 28 '12 at 13:25
  • I think We both need to study regarding GC architecture change w.r.t to 2.3.5 and 4.0.3. I don't remember clearly does 2.3.5 supports concurrent GC ? – AZ_ Sep 28 '12 at 16:56
  • Also please you need to see RAM size of both devices, larger the RAM gets more time GC takes to clear the garbage and produces jitters and lags – AZ_ Sep 28 '12 at 16:57
  • 1
    Good points. I need to continue developing the software, so I can't do more tests right now. If you have any results, let me know. – Thomas Calc Sep 29 '12 at 14:21
  • 1
    I found two good topics regarding the exact same issue please see the following URI http://developer.android.com/tools/help/systrace.html http://developer.android.com/tools/debugging/systrace.html – AZ_ Oct 15 '12 at 07:13
  • Debuging Native Memory Use http://source.android.com/tech/debugging/native-memory.html – AZ_ Oct 15 '12 at 11:49