Write Barriers in JIT Compilers

In languages like Java where garbage collector (GC) is used for cleaning up the unused references. The idea of write barriers is used at runtime to communicate with GC for letting the GC know the set of objects that needs to be tracked due to object pointers getting stored in another object and classes.

The JIT compiler is required to insert a write barrier in the code which it generates to maintain the function correctness.

The write barriers are normally used after following situations:

  • If there is a store of a reference to a field in an object (either static or an instance field).
  • if there is a store of a reference into an array element.
  • If there is an arraycopy of reference.

Having write barriers in the code is an overhead. So there are some optimizations that can be performed to reduce the number of write barriers that are required in the JIT compiled code. JIT performance can be greatly improved if the operations/checks performed by the write-barrier can be inlined.

There are four major checks that are performed in the write barrier.

  1. Nullness Check: Check if a store like (a.f = b) is performed to a null object. This can checked both at compile time and at runtime.

  2. Generational Check: The generational garbage collector divides the heap in two part; a young generation space where the newly created object resides(GC happens frequently) and a tenured space (old generation) where long-lived objects resides (GC doesn’t happen frequently). (Note that the young generation is further divided into one Eden and two Survivor spaces). And now for store statement like (a.f=b) where b is in young generation and a is in old generation, the GC should remember to scan the object (a) stored in old generation.

  3. Heap Object Check: While executing JIT compiled code there is possibility that an object might get allocated on method’s stack-frame. There can be situations where we can not determine at compile time whether the object is on heap or stack. For eg: In the below code obj can point to a heap as well stack object based on the runtime condition and a runtime check is required to determine the same.

        Code:
            void foo(boolean flag) {
                Object obj;
                if (flag)
                    obj = new MyObject();    // stack allocated obejct
                else
                    obj = cachedGlobal;      // heap allocated object
                obj.field = new Object();    // need write barrier conservatively. 
            }
    Note: A stack-allocated object doesn't required a write barrier check. 
  1. Concurrent Mark Check: Marking set of objects for garbage collection can be performed concurrently. A special thread can be used to just mark the set of live objects. However if an object b is stored into an object a that has already been scanned by the concurrent thread then object a need to be rescanned to ensure the functional correctness.