How to interpret an instance's mark word?

1.2k views Asked by At

I am trying to make sense of the output of Java object layout on a 64-bit HotSpot VM (v8). I do not understand how the first three bit of the mark word are used which according to the commentary in the linked class file should indicated weather a biased lock or non-biased lock is set on the instance.

When I analyze an instance of Object using JOL by

ClassLayout layout = ClassLayout.parseClass(Object.class);
Object object = new Object();
System.out.println(layout.toPrintable(object));

I get the following output:

java.lang.Object object internals:
 OFFSET  SIZE  TYPE DESCRIPTION                    VALUE
      0     4       (object header)                01 00 00 00 (0000 0001 0000 0000 0000 0000 0000 0000)
      4     4       (object header)                00 00 00 00 (0000 0000 0000 0000 0000 0000 0000 0000)
      8     4       (object header)                e5 01 00 f8 (1110 0101 0000 0001 0000 0000 1111 1000)
     12     4       (loss due to the next object alignment)
Instance size: 16 bytes (estimated, add this JAR via -javaagent: to get accurate result)
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

From HotSpot's description of the mark word (the first 8 byte), I understand that I should interpret the above bit ranges of the output as follows:

  • 00:01 - Flag for lock (00 in example.)
  • 02:02 - Flag for biased lock (0 in example.)
  • 03:06 - Age as number of young garbage collection iterations. (0000 in example.)
  • 07:07 - Unused. (Seems to be always 1.)
  • 08:39 - Identity hash code. (Only after its computation, before filled with zero.)
  • 40:63 - Unused. (Seems to be filled with zeros.)

Confirming this layout for the hash code range

At least for the hash code, this can be easily confirmed by computing the identity hash code and comparing the values:

ClassLayout layout = ClassLayout.parseClass(Object.class);
Object object = new Object();
// Check that the hash code is not set.
System.out.println(layout.toPrintable(object));

System.out.println("hash code: 0x" + Integer.toHexString(object.hashCode()));
// Confirm that the value is set:
System.out.println(layout.toPrintable(object));

// Compute the hash code manually:
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe unsafe = (Unsafe) field.get(null);
long hashCode = 0;
for (long index = 7; index > 0; index--) {
  hashCode |= (unsafe.getByte(object, index) & 0xFF) << ((index - 1) * 8);
}
System.out.println("hash code: 0x" + Long.toHexString(hashCode));

where the hash code is set as the actual bit (ignoring the fact that it is only represented by 31 bit instead of 32 as the reminder is set to zero). The flags for the locks are all set to zero as expected.

Validating the layout for biased locking

The markOops also gives a layout for when an object is subject to biased locking. Here, the last two bullet points are replaced with the following ranges:

  • 08:09 - Epoch bit
  • 10:63 - Pointer of the thread holding this biased lock.

When I now attempt a biased lock running the following code with -XX:BiasedLockingStartupDelay=0, I can also observe how the biased lock is set. When I run the following code:

ClassLayout layout = ClassLayout.parseClass(Object.class);
Object object = new Object();

// Before using any lock, the layout is as above.
System.out.println(layout.toPrintable(object));

// When acquiring the lock, the thread ID can be seen as below:
synchronized (object) {
  System.out.println(layout.toPrintable(object));
}

// After leaving the lock, the object is biased towards this thread:
System.out.println(layout.toPrintable(object));

The biased lock is visible in the mark word after the initial locking as shown below:

java.lang.Object object internals:
 OFFSET  SIZE  TYPE DESCRIPTION                    VALUE
      0     4       (object header)                05 f0 3f 02 (0000 0101 1111 0000 0011 1111 0000 0010)
      4     4       (object header)                00 00 00 00 (0000 0000 0000 0000 0000 0000 0000 0000)
      8     4       (object header)                e5 01 00 f8 (1110 0101 0000 0001 0000 0000 1111 1000)
     12     4       (loss due to the next object alignment)
Instance size: 16 bytes (estimated, add this JAR via -javaagent: to get accurate result)
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

I can validate that this is not representing the hash code as the mark word changes (the biased lock is revoked) once I invoke object.hashCode().

What I do not understand is that the flags for the biased lock and the non-biased lock are still set to zero. How does HotSpot know that this is a biased lock and not a hash code represented in the object. I additionally wonder: What does the epoch bits indicate?

1

There are 1 answers

0
Rafael Winterhalter On BEST ANSWER

The answer is simple: The OpenJDK's comments are outdated and the mark word is organized differently today.