<< Back to Developer Musings
Garbage Collector Garbage (February 7, 2015)
During the development of Officer Bumble on the Android platform, one of my earliest challenges was dealing with the Java garbage collector. The garbage collector is the nice piece of the JRE that let’s us programmers create objects in memory and then forget about them, freeing us up from the tedium of having to remember to do our deallocations. This can be a both a blessing and a curse because, although it does make our lives easier, it also promotes sloppier code.
The garbage collector actually runs in different phases - there’s a short term cache that’s continually reusing objects in memory, then there’s the larger cache where the JRE puts no longer needed allocations out to pasture. As you might expect, when this larger cache starts to fill up, the JRE needs to free up room. Unfortunately, this doesn’t seem to be a multithreaded operation on Android: the JRE will effectively pause your entire process for up to 1/4 of a second to do this clearing operation, then will resume as normal. This 250ms might not be a huge deal in a business application, but as you can imagine, it wreaks havoc inside of a game engine that only has 16.6ms to run each frame!
What’s the solution to this? The JRE won’t actually queue something up for garbage collection until all references to that object have been removed. If you never remove the references, the garbage collection never runs. This goes against the very nature of Java, but we’re game programmers and we need the predictability.
Here are the major steps I took to avoid being burned by the garbage collector in-game:
- I used object pooling patterns everywhere. Those weapons the criminal throws at you, there’s probably never going to be 20 or 30 of them active at one point in time, so a weapon object pool makes sense. If we do go over our maximum pool size, there’s some code to re-adjust and avoid a crash, but that code could trigger garbage collection, so it’s best to size things correctly from the beginning.
- All memory is pre-allocated. Creating FloatBuffer objects to send vertex data to OpenGL is a very time-heavy operation. Pre-allocating 4K vertices leaves a pretty small memory footprint and also gives us lots of room to play with on each level.
- I only cached or object pooled things that would have caused objects to be sent to the garbage collector every frame. I definitely could have done more, but retrofitting the code after-the-fact would have been a huge headache, so I cheated and I put a System.gc() whenever a static screen pops up. This ensures that the garbage collector won’t accumulate things after say, 50 levels of gameplay have happened.
One final thing I want to mention about garbage collection: make sure you run Android Device Monitor and check out your allocations. You’d be surprised what types of objects your game is allocating without you even knowing. One thing that caused a lot of rewrites was that I found iterating over an ArrayList or a HashMap resulted in Java creating an Iterator object behind the scenes. If you iterate over all your sprites each frame, that’s 60 Iterator objects created and destroyed each second!!! My advice to you here: create your own structured arrays of primitives and do it the old fashioned way. Iterators are a nice convenience, but arrays of primitives are going to avoid the overhead of Iterators and that’s a huge win. To make my life easier, I created an EfficientArray class that handles all of my array operations and abstracts any complexity from the main game engine.
Don’t believe me about Iterators? Check out this allocation report:
Until next time,