0

I've put together something I call a "ViewManager" from a few OpenGL/Android tutorials. The problem is that I am having a memory leak. I get a GC notification every once in awhile and I don't think I am doing anything.

11-23 01:50:34.435: D/dalvikvm(954): GC_CONCURRENT freed 381K, 6% free 8024K/8519K, paused 18ms+6ms, total 64ms

My code starts by extending GLSurfaceView and implementing a CallBack and Runnable. I then have a gameLoop that gets created and started which handles updating the objects.

As you can see from my code, I don't have any objects in the list yet, so the leak is not occuring in the .draw or the .update methods of an object.

I ran the code with taking out the gameLoop and the memory leak appeared to stop. Does anyone have any hints at why I am receiving a memory leak? On a side note, when I remove the Thread.sleep(30) call on the run method, I get the GC_CONCURRENT notification many times faster!

import java.util.LinkedList;
import java.util.ListIterator;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import android.content.Context;
import android.opengl.GLSurfaceView;
import android.opengl.GLU;
import android.view.MotionEvent;


public class ViewManager extends GLSurfaceView implements Runnable, GLSurfaceView.Renderer
{
    private Camera camera;
    private ListIterator<GameObject> objIterator;
    private LinkedList<GameObject> objects;
    private Thread gameThread;
    private boolean running;

    public ViewManager(Context context)
    {
        super(context);
        setRenderer(this);

        objects = new LinkedList<GameObject>();

        camera = new Camera();
        camera.setPosZ(-4.0f);

    }

    public void onDrawFrame(GL10 gl)
    {
        gl.glClear(GL10.GL_DEPTH_BUFFER_BIT|GL10.GL_COLOR_BUFFER_BIT);

        //3D Drawing
        objIterator = objects.listIterator();
        gl.glLoadIdentity();
        camera.draw(gl);    
        while(objIterator.hasNext())
        {
            gl.glPushMatrix();
            objIterator.next().draw(gl);
            gl.glPopMatrix();
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event)
    {
        if(event.getAction() == MotionEvent.ACTION_DOWN)
        {
            camera.getPreviouslyTouchedPoint().set((int)event.getX(),(int)event.getY());
        }

        if(event.getAction() == MotionEvent.ACTION_MOVE)
        {
            camera.setPosX(camera.getPosX() - (camera.getPreviouslyTouchedPoint().x-event.getX())/100f);
            camera.setPosY(camera.getPosY() - (camera.getPreviouslyTouchedPoint().y-event.getY())/100f);
        }

        camera.getPreviouslyTouchedPoint().set((int)event.getX(),(int)event.getY());

        return true;
    }
    public void onSurfaceChanged(GL10 gl, int width, int height)
    {
        running = false;

        gl.glViewport(0,0,width,height);
        gl.glMatrixMode(GL10.GL_PROJECTION);
        gl.glLoadIdentity();
        GLU.gluPerspective(gl, 45.0f, (float)width/(float)height,0.1f,100.0f);
        gl.glMatrixMode(GL10.GL_MODELVIEW);
        gl.glLoadIdentity();

        running = true;
        gameThread = new Thread(this);
        gameThread.start();
    }

    public void onSurfaceCreated(GL10 gl, EGLConfig arg1)
    {
        gl.glShadeModel(GL10.GL_SMOOTH);
        gl.glClearDepthf(1.0f);
        gl.glEnable(GL10.GL_DEPTH_TEST);
        gl.glDepthFunc(GL10.GL_LEQUAL);
        gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_NICEST);
    }

    public void run()
    {
        ListIterator<GameObject> itr = objects.listIterator();

        while(running)
        {
            itr = objects.listIterator();
            while(itr.hasNext())
            {
                itr.next().update(0);
            }
            try
            {
                Thread.sleep(30);
            } catch (InterruptedException e)
            {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

}
Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
Matthew
  • 3,886
  • 7
  • 47
  • 84
  • it appears that just creating the iterator is causing the problems. I commented out the iterator stuff, but left the loop and that is where the Memory leak is happening. – Matthew Nov 23 '12 at 14:57
  • I wrote my own simple linked list and node iteration methods and am now running it with no memory leaks, which makes me really really think that the listIterator() method has a memory leak! (Or I am using it wrong) Very very very interesting... – Matthew Nov 23 '12 at 15:43
  • How do you know you have a memory leak? GC running does not mean you have a leak (it is actually the other way around) – Mohamed_AbdAllah Nov 23 '12 at 23:52
  • hmmm, good question, I guess maybe it isn't a leak, but what I don't understand is why the call to LinkedList<>.listIterator() causes the GC to be called. When I iterate over my custom linked list, the GC never gets called. – Matthew Nov 24 '12 at 11:33

1 Answers1

0

There is always a confusion between a memory leak and GC being run (although they are almost opposite situations). A memory leak happens when there are references to objects that are preventing the GC from collecting these objects. GC collects objects that are no longer referenced by any active Thread (which is the case of your ListIterator object).

Once your thread finish, your ListIterator is no longer referenced by any active thread, so the GC collects it.

To prevent the GC from running, remove the reference yourself before the Thread exits (inside run() method) nulify this object at the end of the run() method, or use a class level Iterator object that you reuse each time (not creating a new object each time the thread runs).

Mohamed_AbdAllah
  • 5,311
  • 3
  • 28
  • 47
  • That makes sense. So every iteration of the loop I am creating a new reference to a new iterator and the old one is left for GC. My question is though, how would I go about "resetting" the current iterator back to the beginning of the LinkedList? The only way I knew was to call the ".listIterator" method. P.S. I hope all is well in Egypt. – Matthew Nov 25 '12 at 04:42
  • You can either set it to Null at the end of the run() method, or you can define it as a class level (global) variable outside run(). This makes it referenced by the main activity, so the GC is only called when the main activity is destroyed. P.S. Thank you very much for your concern :) – Mohamed_AbdAllah Nov 25 '12 at 08:49