Tuesday 21 May 2013

Know Thy Java Object Memory Layout

Sometimes we want to know how an object is laid out in memory, here's 2 use cases and 2 solutions which remove the need to hypothesise.
A few months back I was satisfying my OCD by reading up on java object memory layout. Now Java, as we all know and love, is all about taking care of  such pesky details as memory layout for you. You just leave it to the JVM son, and don't lose sleep over it.
Sometimes though... sometimes you do care. And when you do, here's how to find out.

In theory, theory and practice are the same

Here's an excellent article from a few years back which tells you all about how Java should layout your object, to summarise:
  • Objects are 8 bytes aligned in memory (address A is K aligned if A % K == 0)
  • All fields are type aligned (long/double is 8 aligned, integer/float 4, short/char 2)
  • Fields are packed in the order of their size, except for references which are last
  • Classes fields are never mixed, so if B extends A, an object of class B will be laid out in memory with A's fields first, then B's
  • Sub class fields start at a 4 byte alignment
  • If the first field of a class is long/double and the class starting point (after header, or after super) is not 8 aligned then a smaller field may be swapped to fill in the 4 bytes gap.
The reasons why the JVM doesn't just plonk your fields one after the other in the order you tell it are also discussed in the article, to summarise:
  • Unaligned access is bad, so JVM saves you from bad layout (unaligned access to memory can cause all sorts of ill side effects, including crashing your process on some architectures)
  • Naive layout of your fields would be wasting memory, the JVM reorders fields to improve the overall size of your object
  • JVM implementation requires types to have consistent layout, thus requiring the sub class rules
So... nice clear rules, what could possibly go wrong?

False False Sharing Protection

For one thing, the rules are not part of the JLS, they are just implementation details. If you read Martin Thompson's article about false sharing you'll notice Mr. T had a solution to false sharing which worked on JDK 6, but no longer worked on JDK 7. Here are the 2 versions:

It turns out the JVM changed the way it orders the fields between 6 and 7, and that was enough to break the spell. In fairness there is no rule specified above which requires the fields order to correlate to the order in which they were defined, but ... it's allot to worry about and it can trip you up.
Just as above rules were still fresh in my mind, LMAX (who kindly open sourced the Disruptor) released the Coalescing Ring Buffer. I read through the code and came across the following:

I approached Nick Zeeb on the blog post which introduced the CoalescingRingBuffer and raised my concern that the fields accessed by the producer/consumer might be suffering from false sharing, Nick's reply:
I’ve tried to order the fields such that the risk of false-sharing is minimized. I am aware that Java 7 can re-order fields however. I’ve run the performance test using Martin Thompson’s PaddedAtomicLong instead but got no performance increase on Java 7. Perhaps I’ve missed something so feel free to try it yourself.
Now Nick is a savvy dude, and I'm not quoting him here to criticise him. I'm quoting him to show that this is confusing stuff (so in a way, I quote him to comfort myself in the company of others equally confused professionals). How can we know? here's one way I thought of after talking to Nick:

Using Unsafe I can get the field offset from the object reference, if 2 fields are less than a cache line apart they can suffer from false sharing (depending on the end location in memory). Sure, it's a bit of a hackish way to verify things, but it can become part of your build so in the case of version changes you on't get caught out.
Note that it's the runtime which will determine the layout, not the compiler, so if you build on version X and run on Y it won't help you much.
Enough of that false sharing thing... so negative... why would you care about memory layout apart from false sharing? Here's another example.

The Hot Bunch

Through the blessings of the gods, at about the same time LMAX released the CoalescingRingBuffer, Gil Tene (CTO of Azul) released HdrHistogram. Now Gil is seriously, awesomely, bright and knows more about JVMs than most mortals (here's his InfoQ talk, watch it)  so I was keen to look into his code. And what do you know, a bunch of hot fields:

What Gil is doing here is good stuff, he's trying to get relevant fields to huddle together in memory, which will improve the likelihood of them ending up on the same cache line, saving the CPU a potential cache miss. Sadly the JVM has other plans... 
So here is another tool to help make sense of your memory layout to add to your tool belt: Java Object Layout I bumped into it by accident, not while obsessing about memory layout at all. Here's the output for Histogram:

Note how histogramData jumps to the botton and subBucketMask is moved to the top, breaking up our hot bunch. The solution is ugly but effective, move all fields but the hot bunch to an otherwise pointless parent class:

And the new layout:

Joy! I shall be sending Mr. Tene a pull request shortly :-)

UPDATE 16/01/2014: The excellent JOL has now been released under OpenJDK here. It's even better than before and supports many a funky feature (worthy of a separate post). I've updated the links to point to the new project. Also check out Shipilev's blog post on heap dumps demonstrating the use of this tool.

9 comments:

  1. Did you see BlackHole class from JMH? It uses (for it's inner classes) more interesting padding strategy based on sub/super class fields layout.

    ReplyDelete
    Replies
    1. I didn't and now had both you and Mr. Shipilev point it out and it is a great example. Not sure how you mean more interesting though, it's the same technique but padding both sides instead of one. I totally agree it's one the right ways to avoid false sharing.
      Code discussed is here: http://hg.openjdk.java.net/code-tools/jmh/file/tip/jmh-core/src/main/java/org/openjdk/jmh/logic/BlackHole.java
      Thanks :-)

      Delete
  2. Pull request: https://github.com/giltene/HdrHistogram/pull/6

    ReplyDelete
  3. Thanks teaching me about Java memory layout! I shall add that test and see if I can squeeze out some more performance.

    Just goes to show that open-sourcing your code is an awesome way to get better code and a better understanding :-)

    ReplyDelete
    Replies
    1. Thanks for sharing the Coalescing RB, looking forward to seeing more great contributions from LMAX :-)

      Delete
  4. Just merged your pull request, and added some cleanup/comments. Thanks Nitsan!

    ReplyDelete
    Replies
    1. Thanks :-) Just started using the HdrHistogram in a couple of projects last month, it's a great piece of work.

      Delete
  5. Thanks for the great writing.

    ReplyDelete

Note: only a member of this blog may post a comment.