SHOW ME YOUR ID CARD

As I mentioned at my previous article, in Java, every object has a header that holds some metadata about object itself and type.

Every object (except array) in memory has 2 machine word header. The first one is called mark word. Mark word stores identity hashcode, age, bits used for garbage collection, bits used for locking. To find out more check out the source from OpenJDK.

Lets have a look at the details of object header to refresh our memory since previous article.

 

32 bit JVM:

|----------------------------------------------------------------------------------------|--------------------|
|                                    Object Header (64 bits)                             |        State       |
|-------------------------------------------------------|--------------------------------|--------------------|
|                  Mark Word (32 bits)                  |      Klass Word (32 bits)      |                    |
|-------------------------------------------------------|--------------------------------|--------------------|
| identity_hashcode:25 | age:4 | biased_lock:1 | lock:2 |      OOP to metadata object    |       Normal       |
|-------------------------------------------------------|--------------------------------|--------------------|
|  thread:23 | epoch:2 | age:4 | biased_lock:1 | lock:2 |      OOP to metadata object    |       Biased       |
|-------------------------------------------------------|--------------------------------|--------------------|
|               ptr_to_lock_record:30          | lock:2 |      OOP to metadata object    | Lightweight Locked |
|-------------------------------------------------------|--------------------------------|--------------------|
|               ptr_to_heavyweight_monitor:30  | lock:2 |      OOP to metadata object    | Heavyweight Locked |
|-------------------------------------------------------|--------------------------------|--------------------|
|                                              | lock:2 |      OOP to metadata object    |    Marked for GC   |
|-------------------------------------------------------|--------------------------------|--------------------|

64 bit JVM:

|------------------------------------------------------------------------------------------------------------|--------------------|
|                                            Object Header (128 bits)                                        |        State       |
|------------------------------------------------------------------------------|-----------------------------|--------------------|
|                                  Mark Word (64 bits)                         |    Klass Word (64 bits)     |                    |
|------------------------------------------------------------------------------|-----------------------------|--------------------|
| unused:25 | identity_hashcode:31 | unused:1 | age:4 | biased_lock:1 | lock:2 |    OOP to metadata object   |       Normal       |
|------------------------------------------------------------------------------|-----------------------------|--------------------|
| thread:54 |       epoch:2        | unused:1 | age:4 | biased_lock:1 | lock:2 |    OOP to metadata object   |       Biased       |
|------------------------------------------------------------------------------|-----------------------------|--------------------|
|                       ptr_to_lock_record:62                         | lock:2 |    OOP to metadata object   | Lightweight Locked |
|------------------------------------------------------------------------------|-----------------------------|--------------------|
|                     ptr_to_heavyweight_monitor:62                   | lock:2 |    OOP to metadata object   | Heavyweight Locked |
|------------------------------------------------------------------------------|-----------------------------|--------------------|
|                                                                     | lock:2 |    OOP to metadata object   |    Marked for GC   |
|------------------------------------------------------------------------------|-----------------------------|--------------------|

 

Identity hashcode of the object which is initialized (written into the object header) when it is firstly accessed on demand. As seen from here, if identity hashcode is set, it just returns, otherwise calculation is forwarded toObjectSynchonizer and calculated by considering object’s locking state (biased locked, monitor locked, etc …) as implemented here: http://hg.openjdk.java.net/jdk8/jdk8/hotspot/file/87ee5ee27509/src/share/vm/runtime/synchronizer.cpp#l601

By default Object.hashCode() returns this value if it is not overriden. For getting the generated hashcode of object, System.identityHashCode(obj) is the best way since it directly gets this value from object header via native call so this means that it works also in case of overriden hashCode method. However, when object is locked, the identity hashcode value is moved into the monitor object.

In JVM, there are some different strategies (for production, for testing, etc …) for calculating identity hash code of an object as seen here. In Java 8, the default one is Marsaglia’s xor-shift scheme with thread-specific state as defined here. This approach on Java 8 is based on JVM native threads have thread-local variables (_hashStateX, _hashStateY, _hashStateZ, _hashStateW) to be used for hashcode generation as seen here. So this means that on Java 8, default identity hash code is independent from objects memory location.

You can access the code from here. Here is a code snippet for accessing object’s identity hash code from its header with direct memory access:

// -XX:-UseCompressedOops
public class ObjectIdentityHashCodeDemo {

	public static void main(String[] args) {
		Foo foo = new Foo();
		
		// "hashCode" field is empty and it is initialized when it is firstly accessed on demand
		foo.hashCode();
		
		if (JvmUtil.getReferenceSize() == 4) { // 32 bit JVM
			// | identity_hashcode:25 | age:4 | biased_lock:1 | lock:2 |
			long header = UNSAFE.getInt(foo, 0L);
			int identityHashCode = (int) (header >> 7);
			System.out.println(identityHashCode);
			
		} else { // 64 bit JVM
			// | unused:25 | identity_hashcode:31 | unused:1 | age:4 | biased_lock:1 | lock:2 |
			long header = UNSAFE.getLong(foo, 0L);
			int identityHashCode = (int) ((header >> 8) & 0x7FFFFFFF);
			System.out.println(identityHashCode);
		}

		System.out.println(System.identityHashCode(foo));
	}
	
}

 

We have seen that object’s identity hashcode is generated by JVM if it is not initialized yet. So, what will happen if we clear object header and recall hashCode? Does it return old identity hashcode or generate a new one? Yes, it is initialized with new generated value by JVM since identity hashcode is only store at object header and when we clear object header, there is no way for getting old value by JVM and so it generates new one. So, we can use this behaviour for generating random numbers just for fun🙂

You can access the code from here. Here is a code sample for showing how to use object header via identity hashcode for generating random numbers:

 

public class RandomNumberGeneratorWithObjectIdentityHashCodeDemo {

	private static final Unsafe UNSAFE = JvmUtil.getUnsafe();
	private static final int WARM_UP_RANDOM_NUMBER_COUNT = 1_000_000;
	private static final int RANDOM_NUMBER_COUNT = WARM_UP_RANDOM_NUMBER_COUNT * 100;
	private static final Random RANDOM = new Random();
	
	public static void main(String[] args) {
		generateNumbersWithRandom(WARM_UP_RANDOM_NUMBER_COUNT);
		generateNumbersWithObjectIdentityHashCode(WARM_UP_RANDOM_NUMBER_COUNT);
		
		long start = System.currentTimeMillis();
		generateNumbersWithRandom(RANDOM_NUMBER_COUNT);
		System.out.println("Generating random numbers with 'Random' finished in " + 
				(System.currentTimeMillis() - start) + " milliseconds ...");
		
		start = System.currentTimeMillis();
		generateNumbersWithObjectIdentityHashCode(RANDOM_NUMBER_COUNT);
		System.out.println("Generating random numbers by identity hashcode finished in " + 
				(System.currentTimeMillis() - start) + " milliseconds ...");
	}
	
	private static void generateNumbersWithRandom(int numberCount) {
		for (int i = 0; i < numberCount; i++) {
			RANDOM.nextInt();
		}
	}
	
	private static void generateNumbersWithObjectIdentityHashCode(int numberCount) {
		Foo foo = new Foo();
		if (JvmUtil.getReferenceSize() == 4) { // 32 bit JVM
			// | identity_hashcode:25 | age:4 | biased_lock:1 | lock:2 |
			for (int i = 0; i < numberCount; i++) {
				foo.hashCode();
				// Not set to 0x00. Because if the last 2 bits is set to zero, it means 
				// object is "Lightweight Locked", so the second call will wait forever.
				UNSAFE.putInt(foo, 0L, 0x01);
			}
		} else { // 64 bit JVM
			// | unused:25 | identity_hashcode:31 | unused:1 | age:4 | biased_lock:1 | lock:2 |
			for (int i = 0; i < numberCount; i++) {
				foo.hashCode();
				// Not set to 0x00. Because if the last 2 bits is set to zero, it means 
				// object is "Lightweight Locked", so the second call will wait forever.
				UNSAFE.putLong(foo, 0L, 0x01);
			}
		}
	}
}

 

As stated in demo code as comment, object header should not set to zero. Because, the last 2 bits of object header is used for representing locking state of object.

  • 0x00: Lightweight locked
  • 0x01: Unlocked or Biased (In this case, 3rd LSB is used to determine about if it Biased locked or not locked. If this bit is set to 1, this means that this object is Biased locked to specified thread at header. Otherwise, means that there is no lock with this object
  • 0x02: Heavyweight locked
  • 0x03: Marked for Garbage collection

So, if object header is set to zero, it means that this object is “Lightweight locked” (because the last 2 bit is zero) and the next call on this object forUNSAFE.putLong(foo, …) will wait forever since object header says that object is “Lightweight locked”, but in the header, there is no pointer to lock record. For more details about this subject, please look at this post.

By the way, I will look into details of these locking states at next article and but not diving into them at this post.

And this is the output:

 

Generating random numbers with 'Random' finished in 1394 milliseconds ...
Generating random numbers by resetting identity hashcode finished in 4915 milliseconds ...

 

As shown above, “java.util.Random has 4x performance than resetting and retrieving object’s identity hashcode as random number. However, there maybe some contention based performance issues at multithreaded environments for java.util.Randomusage. And as I stated before, in Java 8, default strategy of identity hashcode generation is independent from object’s memory location and specific to caller thread like java.util.concurrent.ThreadLocalRandom which comes with Java 7. Besides of this blog post’s subject, here is a nice article for comparing java.util.Random and java.util.concurrent.ThreadLocalRandom: http://java-performance.info/java-util-random-java-util-concurrent-threadlocalrandom-multithreaded-environments/

 

REFERENCES

Java Object Header: http://arturmkrtchyan.com/java-object-header

Java Objects Memory Structure: http://www.codeinstructions.com/2008/12/java-objects-memory-structure.html

 

Age of Objects

In Java, every object has a header that holds some metadata about object itself and type. There are two types of object header for non-array typed objects and array typed objects. Array typed objects have an extra header value to define its length.

Every object (except array) in memory has 2 machine word header. The first one is called mark word and the second one is klass word. By the way arrays have extra 32 bit (4 byte) word to represent array’s length information.

Mark word stores identity hashcode, age, bits used for garbage collection, bits used for locking. To find out more check out the source from OpenJDK.

Klass word stores ordinary object pointer (oop) to class metadata, which describes the object’s layout, methods, and other type information. To find out more check out the metadata source from OpenJDK.

Lets have look at the details of object header.

32 bit JVM:

|----------------------------------------------------------------------------------------|--------------------|
|                                    Object Header (64 bits)                             |        State       |
|-------------------------------------------------------|--------------------------------|--------------------|
|                  Mark Word (32 bits)                  |      Klass Word (32 bits)      |                    |
|-------------------------------------------------------|--------------------------------|--------------------|
| identity_hashcode:25 | age:4 | biased_lock:1 | lock:2 |      OOP to metadata object    |       Normal       |
|-------------------------------------------------------|--------------------------------|--------------------|
|  thread:23 | epoch:2 | age:4 | biased_lock:1 | lock:2 |      OOP to metadata object    |       Biased       |
|-------------------------------------------------------|--------------------------------|--------------------|
|               ptr_to_lock_record:30          | lock:2 |      OOP to metadata object    | Lightweight Locked |
|-------------------------------------------------------|--------------------------------|--------------------|
|               ptr_to_heavyweight_monitor:30  | lock:2 |      OOP to metadata object    | Heavyweight Locked |
|-------------------------------------------------------|--------------------------------|--------------------|
|                                              | lock:2 |      OOP to metadata object    |    Marked for GC   |
|-------------------------------------------------------|--------------------------------|--------------------|

64 bit JVM:

|------------------------------------------------------------------------------------------------------------|--------------------|
|                                            Object Header (128 bits)                                        |        State       |
|------------------------------------------------------------------------------|-----------------------------|--------------------|
|                                  Mark Word (64 bits)                         |    Klass Word (64 bits)     |                    |
|------------------------------------------------------------------------------|-----------------------------|--------------------|
| unused:25 | identity_hashcode:31 | unused:1 | age:4 | biased_lock:1 | lock:2 |    OOP to metadata object   |       Normal       |
|------------------------------------------------------------------------------|-----------------------------|--------------------|
| thread:54 |       epoch:2        | unused:1 | age:4 | biased_lock:1 | lock:2 |    OOP to metadata object   |       Biased       |
|------------------------------------------------------------------------------|-----------------------------|--------------------|
|                       ptr_to_lock_record:62                         | lock:2 |    OOP to metadata object   | Lightweight Locked |
|------------------------------------------------------------------------------|-----------------------------|--------------------|
|                     ptr_to_heavyweight_monitor:62                   | lock:2 |    OOP to metadata object   | Heavyweight Locked |
|------------------------------------------------------------------------------|-----------------------------|--------------------|
|                                                                     | lock:2 |    OOP to metadata object   |    Marked for GC   |
|------------------------------------------------------------------------------|-----------------------------|--------------------|

Object age is stored at object’s header and represented with 4 bits at both of 32 bit JVM and 64 bit JVM. Object age defines number of minor garbage collections the object has survived. It is incremented every time an object is copied within the young generation. When the age field reaches the value of maximum tenuring threshold, the object is promoted to the old generation.

Here is a code snippet for accessing object’s age code from its header with direct memory access to show how it is changed with minor GC. You can access the code from here.

// -XX:-UseCompressedOops -XX:+PrintGCDetails -XX:+PrintTenuringDistribution -Xms1g -Xmx1g
public class ObjectAgeDemo {

	public static void main(String[] args) {
		Foo foo = new Foo();
		
		printAddressAndAge(foo);

		Object[] array = new Object[2000000];
		
		for (int i = 0; i < 16; i++) {
			printAddressAndAge(foo);
			int iterationCount = 10 * array.length;
			for (int j = 0; j < iterationCount; j++) {
				array[j % array.length] = new Foo();
			}
		}
	}
	
	private static void printAddressAndAge(Foo foo) {
		System.out.println("Address of object: " + JvmUtil.toHexAddress(JvmUtil.addressOf(foo)));
		if (JvmUtil.getReferenceSize() == 4) { // 32 bit JVM
			// | identity_hashcode:25 | age:4 | biased_lock:1 | lock:2 |
			long header = UNSAFE.getLong(foo, 0L);
			int age = (int) ((header >> 3) & 0x0000000F);
			System.out.println("Age: " + age);
		} else { // 64 bit JVM
			// | unused:25 | identity_hashcode:31 | unused:1 | age:4 | biased_lock:1 | lock:2 |
			long header = UNSAFE.getLong(foo, 0L);
			int age = (int) ((header >> 3) & 0x0000000F);
			System.out.println("Age: " + age);
		}
	}
	
}

As you can see, initial and maximum heap sizes are specified as same value. The reason is that keeping memory locations and limits of generations (young and old) constant for tracking object movement between generations easier via its memory address. If initial and maximum heap sizes are different, generations (heap) can be expanded during runtime as memory consumption and memory locations/limits of generations are changed during runtime.

And this is the output:

Address of object: 0x4354D5F8
Age: 0
Address of object: 0x4354D5F8
Age: 0
[GC (Allocation Failure) 
Desired survivor size 44564480 bytes, new threshold 7 (max 15)
[PSYoungGen: 262144K->43497K(305664K)] 262144K->48692K(1005056K), 0.1136562 secs] [Times: user=0.30 sys=0.00, real=0.11 secs] 
Address of object: 0x4D792418
Age: 1
[GC (Allocation Failure) 
Desired survivor size 44564480 bytes, new threshold 7 (max 15)
[PSYoungGen: 305641K->43513K(305664K)] 310836K->53316K(1005056K), 0.0808406 secs] [Times: user=0.38 sys=0.02, real=0.08 secs] 
Address of object: 0x4F1A86D0
Age: 2
[GC (Allocation Failure) 
Desired survivor size 44564480 bytes, new threshold 7 (max 15)
[PSYoungGen: 305657K->43513K(305664K)] 315460K->57940K(1005056K), 0.0739580 secs] [Times: user=0.26 sys=0.00, real=0.08 secs] 
[GC (Allocation Failure) 
Desired survivor size 44564480 bytes, new threshold 7 (max 15)
[PSYoungGen: 305657K->43513K(305664K)] 320084K->62548K(1005056K), 0.1199197 secs] [Times: user=0.44 sys=0.00, real=0.12 secs] 
Address of object: 0x501FE430
Age: 4
[GC (Allocation Failure) 
Desired survivor size 50331648 bytes, new threshold 6 (max 15)
[PSYoungGen: 305657K->43513K(305664K)] 324692K->67204K(1005056K), 0.0776931 secs] [Times: user=0.34 sys=0.00, real=0.08 secs] 
Address of object: 0x4C70CA38
Age: 5
[GC (Allocation Failure) 
Desired survivor size 49807360 bytes, new threshold 5 (max 15)
[PSYoungGen: 305657K->43513K(295424K)] 329348K->71804K(994816K), 0.0889190 secs] [Times: user=0.42 sys=0.00, real=0.09 secs] 
Address of object: 0x501FE430
Age: 6
[GC (Allocation Failure) 
Desired survivor size 54001664 bytes, new threshold 4 (max 15)
[PSYoungGen: 295417K->31392K(283648K)] 323708K->76482K(983040K), 0.1109815 secs] [Times: user=0.50 sys=0.00, real=0.11 secs] 
Address of object: 0x137B88B0
Age: 6
[GC (Allocation Failure) 
Desired survivor size 56098816 bytes, new threshold 3 (max 15)
[PSYoungGen: 283296K->31424K(292352K)] 328386K->76514K(991744K), 0.2997755 secs] [Times: user=1.39 sys=0.00, real=0.30 secs] 
[GC (Allocation Failure) 
Desired survivor size 57147392 bytes, new threshold 2 (max 15)
[PSYoungGen: 271040K->31456K(271360K)] 316130K->76546K(970752K), 0.3099339 secs] [Times: user=1.93 sys=0.00, real=0.31 secs] 
Address of object: 0x137B88B0
Age: 6
[GC (Allocation Failure) 
Desired survivor size 57671680 bytes, new threshold 1 (max 15)
[PSYoungGen: 271072K->31392K(292352K)] 316162K->76482K(991744K), 0.3062327 secs] [Times: user=1.92 sys=0.00, real=0.31 secs] 
Address of object: 0x137B88B0
Age: 6
[GC (Allocation Failure) 
Desired survivor size 58195968 bytes, new threshold 1 (max 15)
[PSYoungGen: 267936K->31456K(268288K)] 313026K->76546K(967680K), 0.3049041 secs] [Times: user=1.45 sys=0.02, real=0.31 secs] 
Address of object: 0x137B88B0
Age: 6
[GC (Allocation Failure) 
Desired survivor size 58195968 bytes, new threshold 1 (max 15)
[PSYoungGen: 268000K->31360K(292352K)] 313090K->76450K(991744K), 0.3138480 secs] [Times: user=1.73 sys=0.02, real=0.32 secs] 
Address of object: 0x137B88B0
Age: 6
[GC (Allocation Failure) 
Desired survivor size 57671680 bytes, new threshold 1 (max 15)
[PSYoungGen: 266880K->31360K(292352K)] 311970K->76450K(991744K), 0.3634088 secs] [Times: user=2.20 sys=0.02, real=0.36 secs] 
[GC (Allocation Failure) 
Desired survivor size 57147392 bytes, new threshold 1 (max 15)
[PSYoungGen: 266880K->31424K(293376K)] 311970K->76514K(992768K), 0.2759032 secs] [Times: user=1.51 sys=0.00, real=0.28 secs] 
Address of object: 0x137B88B0
Age: 6
[GC (Allocation Failure) 
Desired survivor size 56623104 bytes, new threshold 1 (max 15)
[PSYoungGen: 268480K->31392K(292864K)] 313570K->76482K(992256K), 0.2943370 secs] [Times: user=1.53 sys=0.00, real=0.29 secs] 
Address of object: 0x137B88B0
Age: 6
[GC (Allocation Failure) 
Desired survivor size 56098816 bytes, new threshold 1 (max 15)
[PSYoungGen: 268448K->31424K(294400K)] 313538K->76514K(993792K), 0.2812313 secs] [Times: user=1.56 sys=0.00, real=0.29 secs] 
Address of object: 0x137B88B0
Age: 6
[GC (Allocation Failure) 
Desired survivor size 55050240 bytes, new threshold 1 (max 15)
[PSYoungGen: 270528K->31424K(293888K)] 315618K->76514K(993280K), 0.3355550 secs] [Times: user=2.12 sys=0.01, real=0.34 secs] 
[GC (Allocation Failure) 
Desired survivor size 54001664 bytes, new threshold 1 (max 15)
[PSYoungGen: 270528K->31392K(296448K)] 315618K->76482K(995840K), 0.3548460 secs] [Times: user=2.39 sys=0.00, real=0.36 secs] 
Address of object: 0x137B88B0
Age: 6
[GC (Allocation Failure) 
Desired survivor size 52953088 bytes, new threshold 1 (max 15)
[PSYoungGen: 274080K->31424K(295424K)] 319170K->76514K(994816K), 0.3483227 secs] [Times: user=1.98 sys=0.00, real=0.35 secs] 
Address of object: 0x137B88B0
Age: 6
[GC (Allocation Failure) 
Desired survivor size 52428800 bytes, new threshold 1 (max 15)
[PSYoungGen: 274112K->31424K(297984K)] 319202K->76514K(997376K), 0.2890159 secs] [Times: user=1.39 sys=0.02, real=0.29 secs] 
Heap
 PSYoungGen      total 297984K, used 234705K [0x000000003c700000, 0x0000000051c00000, 0x0000000051c00000)
  eden space 246272K, 82% used [0x000000003c700000,0x0000000048d84740,0x000000004b780000)
  from space 51712K, 60% used [0x000000004e980000,0x0000000050830000,0x0000000051c00000)
  to   space 51200K, 0% used [0x000000004b780000,0x000000004b780000,0x000000004e980000)
 ParOldGen       total 699392K, used 45090K [0x0000000011c00000, 0x000000003c700000, 0x000000003c700000)
  object space 699392K, 6% used [0x0000000011c00000,0x0000000014808948,0x000000003c700000)
 Metaspace       used 5760K, capacity 5784K, committed 5888K, reserved 8192K

As seen from the GC logs,

Heap
 PSYoungGen      total 297984K, used 234705K [0x000000003c700000, 0x0000000051c00000, 0x0000000051c00000)
  eden space 246272K, 82% used [0x000000003c700000,0x0000000048d84740,0x000000004b780000)
  from space 51712K, 60% used [0x000000004e980000,0x0000000050830000,0x0000000051c00000)
  to   space 51200K, 0% used [0x000000004b780000,0x000000004b780000,0x000000004e980000)
 ParOldGen       total 699392K, used 45090K [0x0000000011c00000, 0x000000003c700000, 0x000000003c700000)
  object space 699392K, 6% used [0x0000000011c00000,0x0000000014808948,0x000000003c700000)
 Metaspace       used 5760K, capacity 5784K, committed 5888K, reserved 8192K

The heap is layout like this

Heap Layout

Heap Layout

As seen from the GC logs, after first minor GC, object is moved to Survivor Space 1 from Eden Space. Because at first object address was 0x4354D5F8 (which is a memory location in Eden Space [0x000000003c7000000x000000004b780000]) and then its address was changed to 0x4D792418 (which is a memory location in Survivor Space 1 [0x000000004b7800000x000000004e980000]). This means that object is moved to Survivor Space 1 after first minor GC and its age is incremented to 1 from 0.

Then after each minor GC, object is moved between Survivor spaces and its age is incremented until its age is being bigger than tenuring threshold. There is a maximum value (can be specified with -XX:MaxTenuringThreshold flag and can be 15 at most since age is represented 4 bits) for tenuring threshold and its value (can be initialized with -XX:InitialTenuringThreshold flag and cannot be bigger than its max value) can be changed by JVM during runtime. So when object is being bigger than tenuring threshold value, object is promoted to Tenured Space (also there are some other cases to be promoted to Tenured Space such as not enough space at target (or “To”) Survivor space, some JVM flags may be set like -XX:+AlwaysTenure).

So when you see the logs, as seen, object is moved to Tenured Space at here:

…

Address of object: 0x4C70CA38
Age: 5
[GC (Allocation Failure) 
Desired survivor size 49807360 bytes, new threshold 5 (max 15)
[PSYoungGen: 305657K->43513K(295424K)] 329348K->71804K(994816K), 0.0889190 secs] [Times: user=0.42 sys=0.00, real=0.09 secs] 
Address of object: 0x501FE430
Age: 6
[GC (Allocation Failure) 
Desired survivor size 54001664 bytes, new threshold 4 (max 15)
[PSYoungGen: 295417K->31392K(283648K)] 323708K->76482K(983040K), 0.1109815 secs] [Times: user=0.50 sys=0.00, real=0.11 secs] 
Address of object: 0x137B88B0
Age: 6

...

When object is at 0x4C70CA38 memory location, its age was 5 and its age was still not bigger than tenuring threshold which was 5 at that time. Then after a minor GC, object age was incremented to 6 and at that time it was bigger than tenuring threshold so it was moved to Tenured Space and its memory address was changed to 0x137B88B0 which is a memory location in Tenured Space (0x0000000011c000000x000000003c700000). Then, after others GC (minor and major), object’s age is not changed (always 6) anymore since it has been already promoted to Tenured Space and there is no way back to Young Space.

REFERENCES

Java Object Header: http://arturmkrtchyan.com/java-object-header

Useful JVM Flags – Part 5 (Young Generation Garbage Collection): https://blog.codecentric.de/en/2012/08/useful-jvm-flags-part-5-young-generation-garbage-collection