In GC theory, there is an important property that good collectors try to maintain, heap parsability, that is, shaping the heap in such a way it could be parsed for objects, fields, etc. without complicated metadata supporting it. In OpenJDK, for example, many introspection tasks walk the heap with a simple loop like this:

HeapWord* cur = heap_start; while (cur < heap_used) { object o = (object)cur; do_object(o); cur = cur + o->size(); }

That’s it! If heap is parsable, then we can assume there is a contiguous stream of objects from the start to the allocated end. This is not, strictly speaking, a required property, but it makes GC implementation, testing and debugging much easier.

Enter Thread Local Allocation Buffer (TLAB) machinery: now, each thread has its own TLAB it can currently allocate to. From the GC perspective, this means the entire TLAB is claimed. GC cannot easily know what threads are up to there: are they in the middle of bumping the TLAB cursor? What is the value for TLAB cursor anyway? It is possible that a thread just keeps it somewhere in the register (in OpenJDK, it is not) and never shows it to external observers. So, there is a problem: outsiders do not know what exactly happens in TLABs.

We might want to stop the threads to avoid their TLAB mutation, and then traverse the heap accurately, checking if what we are walking right now is the part of some TLAB. But there is a more convenient trick: why don’t we make heap parsable by inserting filler objects? That is, if we have:

...........|=================== ]............ ^ ^ ^ TLAB start TLAB used TLAB end

…​we can stop the threads, and ask them to allocate a dummy object in the rest of the TLAB to make their part of heap parsable:

...........|===================!!!!!!!!!!!]............ ^ ^ ^ TLAB start TLAB used TLAB end

What is a good candidate for a dummy object? Of course, something that has variable length. Why not int[] array? Note that "putting" the object like this only amounts to putting out the array header, and letting heap mechanics to work out the rest, jumping over its contents. Once thread resumes allocating in TLAB, it can just overwrite whatever filler we allocated, like nothing happened.