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

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s