Garbage Collector Introduce freeze in games

Hello guys,

Developing my new game (based on unlimited gameplay) I’m dealing with garbage collector troubles. During gameplay, the GC try to clean some memory. When it happens, everything freeze for a time like 50ms.

If it seems not a big deal, in a 60 fps game it’s awful. Particularly if the player hits an obstacle just after a freeze => Bad Rates on store.

Some tips can make life better.

1 : Understanding the different types of garbage collector actions :

Thanks to Robert from StackOverflow :

  • GC_FOR_MALLOC / GC_FOR_ALLOC : means that the GC was triggered because there wasn’t enough memory left on the heap to perform an allocation. Might be triggered when new objects are being created.
  • GC_EXPLICIT means that the garbage collector has been explicitly asked to collect, instead of being triggered by high water marks in the heap. Happens all over the place, but most likely when a thread is being killed or when a binder communication is taken down.
  • GC_CONCURRENT : Triggered when the heap has reached a certain amount of objects to collect.
  • GC_EXTERNAL_ALLOC : means that the the VM is trying to reduce the amount of memory used for collectable objects, to make room for more non-collectable.
  • GC_BEFORE_OOM : means that the system is running really low on memory, and that there is a final GC performed, in order to avoid calling the low memory killer.

 

2 : What can we do to prevent it ?

2.a “No new allocation during” rule

The first thing, if you can, is to allocate all things that you’ll need during gameplay before launch a level. Load all objects and never clean them. just use and reuse already created objects.

“Yes but, I will need some items during a play like new enemies appearing, coins or bullets”

One good tip to prepare a level is to load one objectPools for each reusable items (Enemy, bullet shooted, …). This way, you will create new items a minimum time. you can even preload a certain number of items in the pool if you know how much of them will be displayed at the same time.
If you don’t know ObjectPools, please read this great article

2.b Use shared variables to reuse

String text;
public void function(){
// no more : String text = "something";
String text = "something";
}

2.c Use StringBuilder

When concat strings, use StringBuilder instead of “foo”+”bar”. this hint is particularly important when you loop to make a txt += “something”. As usual now, use a Shared StringBuilder instance but take care of the initialization size.

2.d Iterate through lists…

Every time you write this :

for(MyClass object : objects){
object.doSomething()
}

You allocate a new iterator. This allocation seams not a big deal but can become a bigger one if you always do it. Freezes more often.

You should prefer a less readable, but free of any new allocation :

size = objects.size();
for(i=0;i<sizei++) objects.get(i).doSomething();

I use pools but need to delete objets

Using Java, when a object is referenced nowhere, it’s ok for the GC to clean it. It’s a great simple thing. To delete an object I just have to put “object = null” to inform GC that it’s ok to clean it. The fact is that sometimes we prefer choose the good time to clean really an object (and risk little freezes) (see next point). One tip is to keep a reference to the object to delete somewhere then free it at the good time.

Example to keep a ref : make a singleton MyGarbage class

MyGarbages.getInstance().add(object)
// And when it's the good time :
MyGarbages.getInstance().clear();

Please note that changing a referencing target object will perhaps make the first one unreferenced and put in GC :

MyObject obj = new MyObject("foo");
obj = new MyObject("bar");

The first object created (foo) is added to garbage collector. Once again, use your MyGarbages

MyObject obj = new MyObject("foo");
MyGarbages.getInstance().add(obj)
obj = new MyObject("bar");

This time, the garbage collector have nothing to clean.

Do not forget to clear your MyGarbages ! If during a game the memory is not overload, clean when the level is ended or when the game is paused. Otherwise, find good moments to clean your trashes.

2.e Call System.gc();

It seams like an optional point as calling System.gc() does not really force the GC to be cleaned but just send to the system an hint : “Try to clean it now”

But, when you know that at some moments freezes are more “acceptable” by the player, you can try to use it.

Example of moments where a little freeze is “acceptable” :

  • When the player clic “Pause”
  • When the player is invicible
  • When a dialog box ask someting
  • In fact, every times where the player CAN NOT die. 

2.Using logic Threads

Generally we create a thread to do something when we need it that do stuff background. As the thread ended, the GC_EXPLICIT can be called.

To prevent that, you should prefer a Looped thread with an handler linked to it where you can run on demand things to be done.