Exit Full View

Java Garbage Collection Gotcha

I've written a Game Engine in Kotlin (a JVM based language). Last week I decided to switch all the "doubles" to "floats", because I don't need the extra precision. To my surprise, one of my games started dropping frames. WTF, surely floats wouldn't be slower than doubles. I was in a bad mood, and I didn't bother to investigate further, I just reverted those changes.

But then a few days later, after a few unrelated enhancements, it ran slowly again. Now I had to work out why.

It turns out, free heap memory was getting close to zero, but the garbage collector could reclaim just enough memory, so that the JVM didn't need to allocate more (BTW, it was nowhere near the max heap memory setting).

But that meant that the garbage collector was running very frequently, reclaiming tiny amounts of memory each time.

FYI, the game loop is also designed so that one "slow" frame can be compensated for, without loss of smoothness. But two consecutive slow frames will cause "stutter".

The solution was simple - set the initial heap size to a fair amount more than required. The same number of objects need to be garbage collected, but instead of running a sweep every frame, it does so less frequently (collecting more objects per sweep).

In my mind, I think of the JVM getting into a panic when free space is low : "Sh*t, I need to garbage collect right now!". Whereas when there is a good amount of free space, the GC can take its time without panicking!

It's annoying that it panics though, it could allocate a bit more heap memory, and take the leisurely approach. You could argue that it's a trade off between speed and memory usage. However, the heap size remained fixed at about 1/12th of the max setting (indicating that memory isn't at a premium), and the CPU temp shot through the roof (indicating that speed is at a premium)!

Not only is the trade off biased too heavily in favour of saving memory, it also leads to more memory usage. Why? Because my solution was to allocated more heap at application start up (some of which won't ever be used). If every application does this, then we'll end up with a considerable waste of memory! (And god forbid, we may start swapping out - eek!)

Hmm, maybe that last part is overly dramatic. With plenty of free heap (and therefore a leisurely garbage collector), the top end of my heap will never be used, so I expect the OS will happily swap this out without affecting my application.

Floats vs Doubles

The only reason I initially chose to use doubles was because I found it ugly to define float constants like this : 1.0f (the 'f' looks ugly to me). However, I'm now using my own scripting language (Feather) within my game engine, and I can configure the default non-integer data type. Consider the code :

    var foo = 1.0

In Kotlin, foo would be of type Double. In Groovy (using def), it is BigDecimal (yuck!). In Feather, I get to choose. In the context of my game engine, I configure it to mean float. I also use Feather as a scripting language within a CAD program, and there I configure it to mean double.

If I want to be explicit I can use either of these :

    var foo : double = 1.0
    var foo = 1.0d // or 1.0f for a float

Nice.

Un-Revert?

Now the mystery has been solved, I want to go back to using floats. What is the git command the revert a revert? ;-)

I've read lots of blog posts moaning that git is confusing, weird or just plain bad. I barely scratch the surface of its power, (this was the first time I needed to use revert), but my gosh it did exactly what I wanted very easily, without loosing any subsequent code changes, and without losing any history. None of the old fashioned version control programs could do that.

I assume I can now merge HEAD with the "double->float" commits, and I'll be exactly where I want to be. Bloody marvellous. Thanks Linus, Junio Hamano and all the other contributors to git. A wonderful piece of software IMHO!