4

I try to create my small particle system. I have ParticleManager with list of Particles and draw my particles on canvas. I create any new objects like Paint and etc once just in init() function! If particle size is < 0, I remove it:

for (int particle = 0; particle < particles.Count; particle++)
    {
        particles[particle].Update(); //particle size--;
        if (!particles[particle].state) // size > 0 ? true : false
        {
            particles[particle] = null; 
            //here I tried all variations like 
            //((IDisposable)particles[particle]).Dispose();
            //GC.SuppressFinalize(particles[particle]); 
            //System.GC.ReRegisterForFinalize(particles[particle]);
            //((Java.Lang.Object)particles[particle]).Dispose(); and etc             

            particles.Remove(particles[particle]);
        }

Then I create new Particle and add it to my list. What I see in my log:

GC cleanup summary: 1063 objects tested - resurrecting 1002.
GC cleanup summary: 1053 objects tested - resurrecting 992.
...
GC cleanup summary: 1052 objects tested - resurrecting 988.
46800 outstanding GREFs. Performing a full GC!

And then I have 10-15(!!!) second pause in my render thread!!! I read official documentation, but it hasn't any solution. I analysed and compared my code with mono JetBoy example, but JetBoy's log hasn't anything about GC. Although I wrote my program with JetBoy's example. How to fix full GC problem?


Edit: MainThread.cs

public override void Run()
    {
        Log.Verbose("Run()", "r");
        Canvas c;
        while (mRun) {
            c = null;
            mPassedTime = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;
            if (mTimerTask == null) {
                mTimerTask = new CountDownTimerTask(this);
                mTimer.Schedule(mTimerTask, mTaskIntervalInMillis);
            }
            try {
                c = mSurfaceHolder.LockCanvas(null);

                lock (mSurfaceHolder)
                    DoDrawRunning(c);
            } finally {
                if (c != null)
                    mSurfaceHolder.UnlockCanvasAndPost(c);
            }
        }
    }
    private void DoDrawRunning(Canvas canvas)
    {
        #region particles
        for (int eng = 0; eng < engines.Count; eng++)
        {   
            engines[eng].Update();
            engines[eng].Draw(canvas);
        }
        #endregion
    }

ParticleEnginee.cs

public void Update() {
        if (particles.Count < maxTotal) {
            for (int i = 0; i < total; i++) {
                if (addNewB)
                    particles.Add(GenerateNewParticle()); // return new Particle
            }
        }

        for (int particle = 0; particle < particles.Count; particle++) {
            particles[particle].Update(); // position and size update
            if (!particles[particle].state)  // size > 0 ?
                particles.RemoveAt(particle);
        }
    }
public void Draw(Canvas canvas) {
        for (int j = 0; j < 3; j++)  // 3 particle color-levels draw
            for (int index = 0; index < particles.Count; index++) 
                particles[index].Draw(canvas, j);
    }

Particle.cs

public void Draw(Canvas canvas) {
    mPaint.StrokeWidth = mSize;
    mPaint.Color = Color.Blue;
    canvas.DrawPoint(posX, posY, mPaint);
}
Leo
  • 3,003
  • 5
  • 38
  • 61
  • Have you tried to manually call GC.Collect() periodically? (in your render thread for instance) see http://docs.xamarin.com/guides/android/advanced_topics/garbage_collection#Helping_the_GC – Julien Apr 29 '13 at 10:16
  • 2
    You should use weak WeakReference class for your particles – Artem Zelinskiy Apr 29 '13 at 16:35
  • @Julien, yes and it's give me lags. To Greensy: good idea, I'll try it – Leo Apr 29 '13 at 18:39

1 Answers1

7

The primary problem is that you have too many Java.Lang.Object instances alive at once, as each Java.Lang.Object instance contributes to the JNI global reference count, which also contributes to GC overhead. Want to reduce GC overhead? Reduce the number of GREFs you use.

You can track the GREF count by enabling gref logging

adb shell setprop debug.mono.log gref

I assume that Particle is a Java.Lang.Object subclass, meaning you have at least particles.Count GREFs alive at once.

The solution is to not make Particle a Java.Lang.Object subclass, and alter your architecture in any way to ensure that Particle isn't one.

If Particle isn't a Java.Lang.Object instance, then we lack sufficient information to reproduce the problem and suggest a solution. GREF logging output would be a handy start (how many GREFs do you have at once?), as it will also help you determine which types are being created so you can consider reducing their lifetime.

This guide on reading gref log output may also be handy.

jonp
  • 13,512
  • 5
  • 45
  • 60