Tuesday, August 20, 2013

Joda Time's Memory Issue in Android

I've recently gotten fed up with how nuts the built-in calendar library is for Java/Android.  It's incredibly easy to make mistakes and it's unintuitive at best, so I've finally decided to take the plunge and switch to Joda time.

Joda is like a dream come true* except for one fairly extreme memory issue that I ran into.  After adding it to the app we started to see two huge memory sinks show up in MAT: a JarFile and a ZipFile that in our app took up a combined 4MB!



Joda's JAR is only half a meg, so how come these things took up so much space?  Why didn't any other JARs take up this space?  What's even stranger is that the amount of memory used seemed to scale based on the number of resources I had in the application; a simple test app only used up an extra 700kb, but Expedia's resource-heavy app took up the above.

It turns out the problem is ClassLoader.getResourceAsStream().  Joda time includes the olson timezone database in the JAR itself and loads the TZ data dynamically through getResourceAsStream().  For some reason getResourceAsStream() does some rather extreme caching and takes up a ton of memory if you use it**.

Thankfully there's a fairly simple solution.  You can actually implement any timezone Provider you want, circumventing the normal JAR-based ZoneInfoProvider.  Just make sure that your implementation has a default constructor and setup your system properties thus:

System.setProperty("org.joda.time.DateTimeZone.Provider",
    AssetZoneInfoProvider.class.getCanonicalName());

As such, I imported all of the TZ data (compiled, from the JAR) into my project's /assets/ directory.  Then I took the source for ZoneInfoProvider and reworked it so that openResource() uses the AssetManager to retrieve data.  I hooked it up and voila - no more excessive memory usage!  As an added bonus, this makes it a lot easier to update your TZ data without relying on a new version of Joda time.

As an epilogue, if someone can explain why getResourceAsStream() causes the sadness it does I'd be interested to know.  I tried looking into it for a bit but gave up because it wasn't like I would be able to change the system code anyways.

* Seriously: if you deal with dates, times, or some combination thereof at all, you will be doing yourself a favor by switching to Joda time.

** What initially tipped me off was a Jackson XML post about the same problem: https://github.com/FasterXML/jackson-core/pull/49