As it might be too daunting to read the entire implementation , this section tries to introduce it piece by piece.

Some parts of runtime, especially those parts that deal with references to Java heap, get broken by GC, so we need to notify them that GC is about to act. This would let those subsystems to prepare/save parts of their state before impending GC moves.

Threads need to give up their TLABs and ask GC for new ones after GC finishes.

Since we are going to use a marking bitmap to track what objects are reachable, we need to clean it up before using it. Or, in this case, as we pursue the goal of never taking resources until the GC cycle hits, we need to commit the bitmap to memory first. This comes with a few interesting advantages, at least on Linux, where most of the bitmap would be mapped to zero page, especially for sparse heap.

GC usually needs to do a few things in preparation for GC. Read the comments, they should be self-explanatory:

3.2. Marking

Stop-the-world marking is quite simple once we have all the pieces ready to go. Marking is pretty generic, and it would normally be the first step in many GC implementations.

{ GCTraceTime(Info, gc) time("Step 1: Mark", NULL); // Marking stack and the closure that does most of the work. The closure // would scan the outgoing references, mark them, and push newly-marked // objects to stack for further processing. EpsilonMarkStack stack; EpsilonScanOopClosure cl(&stack, &_bitmap); // Seed the marking with roots. process_roots(&cl); stat_reachable_roots = stack.size(); // Scan the rest of the heap until we run out of objects. Termination is // guaranteed, because all reachable objects would be marked eventually. while (!stack.is_empty()) { oop obj = stack.pop(); obj->oop_iterate(&cl); stat_reachable_heap++; } // No more derived pointers discovered after marking is done. DerivedPointerTable::set_active(false); }

It is like every other graph walking problem: you start from the initial set of reachable vertices, walk all outbound edges, recording which vertices you visited, and do this until you run out of unvisited verticies. In GC, "vertexes" are the objects, and "edges" are the references between them.

Technically, we could have just used recursion to walk the object graph, but it is a bad idea for arbitrary graphs, which can have very large diameters. Think about walking the linked list with 1 billion nodes in it. So, to limit the recursion depth, we use marking stack that records objects discovered.

The initial set of reachable objects comes from GC roots. Don’t dwell on what process_roots does for now, we will visit it later. So far, assume it walks all references reachable from the VM side.

The marking bitmap serves both as the thing that tracks the marking wavefront (the set of already visited objects), and in the end gives us the desired output: the set of all reachable objects. The actual work in done in the EpsilonScanOopClosure , that would be applied to all interesting objects, and which iterates all references in a given object, and here it is:

class EpsilonScanOopClosure : public BasicOopIterateClosure { private: EpsilonMarkStack* const _stack; MarkBitMap* const _bitmap; template <class T> void do_oop_work(T* p) { // p is the pointer to memory location where oop is, load the value // from it, unpack the compressed reference, if needed: T o = RawAccess<>::oop_load(p); if (!CompressedOops::is_null(o)) { oop obj = CompressedOops::decode_not_null(o); // Object is discovered. See if it is marked already. If not, // mark and push it on mark stack for further traversal. Non-atomic // check and set would do, as this closure is called by single thread. if (!_bitmap->is_marked(obj)) { _bitmap->mark((HeapWord*)obj); _stack->push(obj); } } } };

After this step is done, _bitmap contains the bits set at the locations where alive objects are. This gives us the opportunity to walk all live objects, like this: