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

4 comments:

  1. Have you had issues with startup time after switching to Joda?

    ReplyDelete
  2. Is there something that can be added to the main Joda-Time jar file? Feel free to propose a pull request if you think there is.

    ReplyDelete
    Replies
    1. One could make ZoneInfoProvider more customizable; that way one could move the tzdata files elsewhere without having to copy all of its code as well. You'd also want to compile a version of the jar that didn't include the tzdata (so that you don't end up inflating file size, important for apps).

      Even with those changes things still get complicated for your average Android developer because then they have to make sure to include the right resources in the right spot so that the customized ZoneInfoProvider picks it up. That's why I made joda-time-android; I thought a lot about the problem and went with the simplest solution for an Android developer who wants to pick up Joda-Time (one dependency + one line of code).

      Delete